.wrap_lazy-videos { display: flex; flex-wrap: wrap; gap: 1rem; .wrap_lazy-video { inline-size: fit-content; } } document.addEventListener("DOMContentLoaded", () => { document.querySelectorAll(".wrap_lazy-videos .wrap_lazy-video").forEach(function(wrap_video) { var video_path = wrap_video.textContent; var dokuwiki_path = video_path; var src_path = ("/_media/" + video_path).replaceAll(":", "/"); var basename = src_path.split('/').reverse()[0]; var html = ''; wrap_video.innerHTML = html; }); const videos = document.querySelectorAll(".wrap_lazy-videos .wrap_lazy-video video"); // Track state to avoid repeated work const state = new WeakMap(); // Batch updates per frame let pending = new Set(); let scheduled = false; const schedule = (video, isVisible) => { pending.add({ video, isVisible }); if (!scheduled) { scheduled = true; requestAnimationFrame(() => { for (const item of pending) { if (item.isVisible) { loadVideo(item.video); } else { unloadVideo(item.video); } } pending.clear(); scheduled = false; }); } }; const loadVideo = (video) => { const s = state.get(video); if (s === "loaded") return; const source = video.querySelector("source[data-src]"); if (!source) return; source.src = source.dataset.src; video.load(); if (video.autoplay) { video.play().catch(() => {}); } state.set(video, "loaded"); }; const unloadVideo = (video) => { const s = state.get(video); if (s === "unloaded") return; video.pause(); video.currentTime = 0; const source = video.querySelector("source"); if (source && source.src) { source.dataset.src = source.src; source.removeAttribute("src"); } video.load(); state.set(video, "unloaded"); }; // Visibility debounce per element const visibilityTimers = new WeakMap(); const observer = new IntersectionObserver( (entries) => { for (const entry of entries) { const video = entry.target; // Clear previous debounce if (visibilityTimers.has(video)) { clearTimeout(visibilityTimers.get(video)); } // Debounce visibility changes const t = setTimeout(() => { const isVisible = entry.isIntersecting; // Optional buffer: avoid unload too aggressively schedule(video, isVisible); }, 120); // 100–150ms is typical visibilityTimers.set(video, t); } }, { root: null, threshold: 0.25, rootMargin: "300px 0px" // preload slightly before visible } ); videos.forEach((v) => { state.set(v, "unloaded"); observer.observe(v); }); });