(function () { var root = document.querySelector("[data-hero]"); if (!root) return; var slides = Array.prototype.slice.call(root.querySelectorAll("[data-hero-slide]")); var thumbs = Array.prototype.slice.call(root.querySelectorAll("[data-hero-index]")); var currentEl = root.querySelector("[data-hero-current]"); var fillEl = root.querySelector(".hero__pager-fill"); var bgEl = root.querySelector("[data-hero-bg]"); var total = slides.length; if (!total) return; var index = 0; var timer = null; var interval = 3000; var bgToken = 0; var touchStartX = null; var touchStartY = null; var mouseStartX = null; var mouseStartY = null; var mouseDown = false; var suppressClickUntil = 0; var posterPreloadTimer = null; var bgLayers = []; var activeBgIndex = 0; if (bgEl) { var alternateBg = bgEl.cloneNode(false); alternateBg.classList.remove("is-active", "lazy"); alternateBg.removeAttribute("data-hero-bg"); alternateBg.removeAttribute("data-src"); alternateBg.removeAttribute("data-lazy-eager"); alternateBg.removeAttribute("data-decrypted"); alternateBg.removeAttribute("data-decrypt-failed"); alternateBg.removeAttribute("fetchpriority"); alternateBg.src = "/static/images/placeholder.png?v=2"; bgEl.parentNode.insertBefore(alternateBg, bgEl.nextSibling); bgLayers = [bgEl, alternateBg]; } // 初始 HTML 只展示首张;脚本就绪后改为叠层,由 CSS 完成平滑交叉过渡。 slides.forEach(function (slide, i) { slide.removeAttribute("hidden"); slide.setAttribute("aria-hidden", i === index ? "false" : "true"); }); function pad(n) { return n < 10 ? "0" + n : String(n); } function slideBgSrc(slide) { var media = slide.querySelector("[data-hero-media]"); if (!media) return ""; return media.getAttribute("data-hero-media") || media.getAttribute("data-src") || ""; } function loadSlidePoster(slideIndex) { var slide = slides[slideIndex]; if (!slide) return; var poster = slide.querySelector("[data-hero-poster]"); if (!poster || poster.getAttribute("data-hero-loaded") === "1" || poster.getAttribute("data-hero-loading") === "1") { return; } var src = poster.getAttribute("data-src") || ""; if (!src) return; poster.setAttribute("data-hero-loading", "1"); function done() { poster.removeAttribute("data-hero-loading"); poster.setAttribute("data-hero-loaded", "1"); } if (typeof window.processImage === "function") { window.processImage(src, poster).then(done); return; } poster.addEventListener("load", done, { once: true }); poster.addEventListener("error", done, { once: true }); poster.src = src; } function queueNextPoster() { if (posterPreloadTimer) { window.clearTimeout(posterPreloadTimer); } if (total < 2) return; posterPreloadTimer = window.setTimeout(function () { loadSlidePoster((index + 1) % total); }, 600); } function syncBg(slide) { if (!bgLayers.length) return; var src = slideBgSrc(slide); if (!src) return; var currentBg = bgLayers[activeBgIndex]; var nextBgIndex = activeBgIndex === 0 ? 1 : 0; var targetBg = bgLayers[nextBgIndex]; if (currentBg.getAttribute("data-src") === src) { targetBg = currentBg; nextBgIndex = activeBgIndex; } targetBg.setAttribute("data-src", src); targetBg.removeAttribute("data-decrypted"); targetBg.removeAttribute("data-decrypt-failed"); var token = ++bgToken; function reveal(result) { if (token !== bgToken) return; if (result) { targetBg.setAttribute("data-decrypted", "1"); } if (nextBgIndex === activeBgIndex) return; window.requestAnimationFrame(function () { if (token !== bgToken) return; targetBg.classList.add("is-active"); currentBg.classList.remove("is-active"); activeBgIndex = nextBgIndex; }); } if (typeof window.processImage === "function") { // 必须走解密;禁止把加密原址直接赋给 src window.processImage(src, targetBg).then(reveal); return; } targetBg.src = src; reveal(src); } function goTo(next) { var nextIndex = ((next % total) + total) % total; if (nextIndex === index) return; index = nextIndex; loadSlidePoster(index); queueNextPoster(); slides.forEach(function (slide, i) { var active = i === index; slide.classList.toggle("is-active", active); slide.setAttribute("aria-hidden", active ? "false" : "true"); if (active) { syncBg(slide); } }); thumbs.forEach(function (thumb, i) { var active = i === index; thumb.classList.toggle("is-active", active); thumb.setAttribute("aria-selected", active ? "true" : "false"); }); if (currentEl) currentEl.textContent = pad(index + 1); if (fillEl) fillEl.style.width = ((index + 1) / total) * 100 + "%"; } function next() { goTo(index + 1); } function previous() { goTo(index - 1); } function start() { stop(); if (total < 2 || mouseDown || touchStartX !== null) return; timer = window.setInterval(next, interval); } function stop() { if (timer) { window.clearInterval(timer); timer = null; } } thumbs.forEach(function (thumb) { var thumbIndex = Number(thumb.getAttribute("data-hero-index")); function preloadThumbSlide() { if (!Number.isNaN(thumbIndex)) { loadSlidePoster(thumbIndex); } } thumb.addEventListener("mouseenter", preloadThumbSlide); thumb.addEventListener("focus", preloadThumbSlide); thumb.addEventListener("touchstart", preloadThumbSlide, { passive: true }); thumb.addEventListener("click", function () { if (Number.isNaN(thumbIndex)) return; goTo(thumbIndex); start(); }); }); var canHover = !window.matchMedia || window.matchMedia("(hover: hover)").matches; if (canHover) { root.addEventListener("mouseenter", stop); root.addEventListener("mouseleave", start); } root.addEventListener("dragstart", function (event) { event.preventDefault(); }); root.addEventListener("mousedown", function (event) { if (event.button !== 0) return; mouseStartX = event.clientX; mouseStartY = event.clientY; mouseDown = true; root.classList.add("is-dragging"); stop(); }); window.addEventListener("mousemove", function (event) { if (!mouseDown || mouseStartX === null || mouseStartY === null) return; var deltaX = event.clientX - mouseStartX; var deltaY = event.clientY - mouseStartY; if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) >= 6) { event.preventDefault(); } }); window.addEventListener("mouseup", function (event) { if (!mouseDown || mouseStartX === null || mouseStartY === null) return; var deltaX = event.clientX - mouseStartX; var deltaY = event.clientY - mouseStartY; var isHorizontalSwipe = Math.abs(deltaX) >= 50 && Math.abs(deltaX) > Math.abs(deltaY) * 1.2; mouseStartX = null; mouseStartY = null; mouseDown = false; root.classList.remove("is-dragging"); if (isHorizontalSwipe) { suppressClickUntil = Date.now() + 400; if (deltaX < 0) { next(); } else { previous(); } } start(); }); window.addEventListener("blur", function () { if (!mouseDown) return; mouseStartX = null; mouseStartY = null; mouseDown = false; root.classList.remove("is-dragging"); start(); }); root.addEventListener( "touchstart", function (event) { if (event.touches.length !== 1) return; touchStartX = event.touches[0].clientX; touchStartY = event.touches[0].clientY; stop(); }, { passive: true } ); root.addEventListener( "touchend", function (event) { if (touchStartX === null || touchStartY === null || !event.changedTouches.length) { start(); return; } var touch = event.changedTouches[0]; var deltaX = touch.clientX - touchStartX; var deltaY = touch.clientY - touchStartY; var isHorizontalSwipe = Math.abs(deltaX) >= 50 && Math.abs(deltaX) > Math.abs(deltaY) * 1.2; touchStartX = null; touchStartY = null; if (isHorizontalSwipe) { event.preventDefault(); suppressClickUntil = Date.now() + 400; if (deltaX < 0) { next(); } else { previous(); } } start(); }, { passive: false } ); root.addEventListener( "touchcancel", function () { touchStartX = null; touchStartY = null; start(); }, { passive: true } ); root.addEventListener( "click", function (event) { if (Date.now() >= suppressClickUntil) return; event.preventDefault(); event.stopPropagation(); }, true ); document.addEventListener("visibilitychange", function () { if (document.hidden) { stop(); } else { start(); } }); if (fillEl) fillEl.style.width = (1 / total) * 100 + "%"; loadSlidePoster(index); queueNextPoster(); // 首帧也走解密;与 imgDecypt 的 eager 加载可共享 decryptedURLCache syncBg(slides[0]); start(); })();