Files
class-history-template/public/js/pages.js
T

86 lines
3.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ================= 星空背景 ================= */
(function () {
const canvas = document.getElementById('stars');
const ctx = canvas.getContext('2d');
let w, h, stars = [], dpr = Math.min(window.devicePixelRatio || 1, 2);
function resize() {
w = canvas.width = window.innerWidth * dpr;
h = canvas.height = window.innerHeight * dpr;
canvas.style.width = window.innerWidth + 'px';
canvas.style.height = window.innerHeight + 'px';
const count = Math.min(160, Math.floor(window.innerWidth / 9));
stars = Array.from({ length: count }, () => ({
x: Math.random() * w,
y: Math.random() * h,
r: (Math.random() * 1.4 + .3) * dpr,
a: Math.random() * .6 + .15,
s: Math.random() * .0009 + .0003,
dy: Math.random() * .05 + .015
}));
}
let t = 0;
function draw() {
ctx.clearRect(0, 0, w, h);
t += 1;
for (const s of stars) {
const alpha = s.a + Math.sin(t * s.s * 12) * .25;
ctx.beginPath();
ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255,244,214,' + Math.max(0.03, Math.min(1, alpha)) + ')';
ctx.fill();
s.y += s.dy * dpr;
if (s.y > h + 5) { s.y = -5; s.x = Math.random() * w; }
}
requestAnimationFrame(draw);
}
resize();
window.addEventListener('resize', resize);
draw();
})();
/* ================= 滚动出现 ================= */
(function () {
const els = document.querySelectorAll('.reveal');
if (!('IntersectionObserver' in window)) {
els.forEach(e => e.classList.add('visible'));
return;
}
const STEP = 70; // 同一批元素之间每级错峰延时
const MAX_STEP = 6; // 最多累加到第 6 级,再多就保持同一延时
const io = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const el = entry.target;
// 延时只在这里算一次,且必须随「当前文档顺序」单调不减:
// 用 idx % 6 会让第 7 个元素的延时回到 0,比第 6 个更先出现。
// 歌单会先按当天顺序重排 DOM(music-shuffle.js),所以这里读到的
// 位置就是访客看到的位置,入场顺序与列表顺序一致。
const idx = Array.from(el.parentElement.children).indexOf(el);
const delay = Math.min(idx, MAX_STEP) * STEP;
setTimeout(() => el.classList.add('visible'), delay);
io.unobserve(el);
}
});
}, { threshold: .1, rootMargin: '0px 0px -60px 0px' });
els.forEach(e => io.observe(e));
})();
/* ================= 进度条 + 回顶部 ================= */
(function () {
const bar = document.getElementById('progress');
const top = document.getElementById('top');
if (!bar && !top) return;
function onScroll() {
const st = window.scrollY || document.documentElement.scrollTop;
const max = document.documentElement.scrollHeight - window.innerHeight;
if (bar) bar.style.width = (max > 0 ? (st / max) * 100 : 0) + '%';
if (top) top.classList.toggle('show', st > 500);
}
window.addEventListener('scroll', onScroll, { passive: true });
onScroll();
if (top) top.addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' }));
})();