reveal.js (2236B)
1 const SKIP = new Set(["STYLE", "SCRIPT", "TEMPLATE", "NOSCRIPT"]); 2 const ATOMIC = new Set(["PRE", "IMG", "SVG", "VIDEO", "CANVAS", "IFRAME", "TABLE"]); 3 const BLOCKISH = /^(block|flex|grid|list-item|table|flow-root)$/; 4 const BUDGET_MS = 450; 5 const MAX_STEP_MS = 14; 6 7 const pending = new Map(); 8 9 export function revealLines(root) { 10 if (!root || window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; 11 12 for (const [r] of pending) { 13 if (r === root || r.contains(root) || root.contains(r)) cleanup(r); 14 } 15 16 const lines = collectLines(root).filter(onScreen); 17 if (lines.length === 0) return; 18 19 const step = Math.min(MAX_STEP_MS, BUDGET_MS / lines.length); 20 root.style.setProperty("--line-ms", `${step}ms`); 21 lines.forEach((el, i) => { 22 el.style.setProperty("--line", String(i)); 23 el.classList.add("line-in"); 24 }); 25 26 const timer = window.setTimeout(() => cleanup(root), step * lines.length + 50); 27 pending.set(root, { lines, timer }); 28 } 29 30 function cleanup(root) { 31 const entry = pending.get(root); 32 if (!entry) return; 33 pending.delete(root); 34 window.clearTimeout(entry.timer); 35 root.style.removeProperty("--line-ms"); 36 for (const el of entry.lines) { 37 el.classList.remove("line-in"); 38 el.style.removeProperty("--line"); 39 } 40 // hidden lines drop focus, so anything that wants it can take it back now 41 document.dispatchEvent(new CustomEvent("reveal:done")); 42 } 43 44 function onScreen(el) { 45 const r = el.getBoundingClientRect(); 46 return r.bottom > 0 && r.top < window.innerHeight && r.width > 0; 47 } 48 49 function collectLines(root) { 50 const lines = []; 51 const walk = (el) => { 52 for (const child of el.children) { 53 if (SKIP.has(child.tagName)) continue; 54 if (getComputedStyle(child).display === "none") continue; 55 if (isLine(child)) { 56 lines.push(child); 57 } else { 58 walk(child); 59 } 60 } 61 }; 62 walk(root); 63 return lines; 64 } 65 66 function isLine(el) { 67 if (ATOMIC.has(el.tagName.toUpperCase())) return true; 68 if (el.classList.contains("tags") || el.classList.contains("tag")) return true; 69 for (const child of el.children) { 70 if (SKIP.has(child.tagName)) continue; 71 if (BLOCKISH.test(getComputedStyle(child).display)) return false; 72 } 73 return true; 74 }