338 lines
9.2 KiB
JavaScript
338 lines
9.2 KiB
JavaScript
(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();
|
|
})();
|