.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);
});
});