astrion-website/demo/site-assets/site.js

277 lines
8.8 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.

/* Astrion 官网主页交互
* 1) 多层视差星空(无连线、无光晕:实心星点 + 闪烁 + 极慢漂移 + 鼠标/滚动视差 + 偶发流星)
* 2) 顶栏滚动态
* 3) FAQ 手风琴平滑展开details + 高度动画)
* 4) 演示区 iframe 等比缩放(内部固定 1400×860 逻辑尺寸)
* 5) 挂载品牌眼睛(眨眼 + 鼠标追踪)
*/
(function () {
'use strict';
var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
/* ═══════════ 1) 星空 ═══════════ */
var canvas = document.getElementById('starfield');
var ctx = canvas.getContext('2d');
var W = 0;
var H = 0;
var DPR = Math.min(window.devicePixelRatio || 1, 2);
// 层:远 → 近。count 按屏幕面积缩放drift 为每秒下落 pxparallax 为鼠标/滚动视差系数
var LAYERS = [
{ density: 1 / 5200, sizeMin: 0.4, sizeMax: 0.9, drift: 1.2, parallax: 6, twinkle: 0.35, alpha: 0.55 },
{ density: 1 / 9800, sizeMin: 0.7, sizeMax: 1.3, drift: 2.6, parallax: 14, twinkle: 0.55, alpha: 0.75 },
{ density: 1 / 21000, sizeMin: 1.1, sizeMax: 1.9, drift: 4.5, parallax: 26, twinkle: 0.8, alpha: 1.0 }
];
var stars = [];
var meteors = [];
var nextMeteorAt = 0;
function resize() {
W = window.innerWidth;
H = window.innerHeight;
canvas.width = Math.round(W * DPR);
canvas.height = Math.round(H * DPR);
canvas.style.width = W + 'px';
canvas.style.height = H + 'px';
ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
seed();
if (reduceMotion) draw(0, 0, 0);
}
function seed() {
stars = [];
var area = W * H;
for (var li = 0; li < LAYERS.length; li++) {
var L = LAYERS[li];
var n = Math.max(12, Math.round(area * L.density));
for (var i = 0; i < n; i++) {
stars.push({
layer: li,
x: Math.random() * W,
y: Math.random() * H,
r: L.sizeMin + Math.random() * (L.sizeMax - L.sizeMin),
// 闪烁相位与速度(部分星几乎不闪,画面才稳)
phase: Math.random() * Math.PI * 2,
speed: 0.25 + Math.random() * 0.9,
amp: 0.25 + Math.random() * 0.75
});
}
}
}
// 鼠标视差目标值
var mouseTx = 0;
var mouseTy = 0;
var mouseX = 0;
var mouseY = 0;
window.addEventListener('mousemove', function (e) {
mouseTx = e.clientX / W - 0.5;
mouseTy = e.clientY / H - 0.5;
}, { passive: true });
function scheduleMeteor(now) {
// 2248 秒一颗,保持稀有
nextMeteorAt = now + 22000 + Math.random() * 26000;
}
function spawnMeteor() {
var fromLeft = Math.random() < 0.5;
var startX = fromLeft ? -40 : W * (0.35 + Math.random() * 0.65);
var startY = Math.random() * H * 0.4;
var angle = fromLeft ? 0.32 + Math.random() * 0.2 : Math.PI - (0.32 + Math.random() * 0.2);
var speed = 380 + Math.random() * 240;
meteors.push({
x: startX,
y: startY,
vx: Math.cos(angle) * speed,
vy: Math.abs(Math.sin(angle)) * speed,
life: 0,
ttl: 0.7 + Math.random() * 0.35
});
}
var lastTs = 0;
function tick(ts) {
if (document.hidden) {
lastTs = ts;
requestAnimationFrame(tick);
return;
}
var dt = Math.min((ts - lastTs) / 1000 || 0, 0.05);
lastTs = ts;
var now = ts;
// 鼠标视差缓动
mouseX += (mouseTx - mouseX) * 0.04;
mouseY += (mouseTy - mouseY) * 0.04;
if (now >= nextMeteorAt) {
spawnMeteor();
scheduleMeteor(now);
}
draw(dt, ts, window.scrollY || 0);
// 流星生命推进
for (var i = meteors.length - 1; i >= 0; i--) {
var m = meteors[i];
m.life += dt;
m.x += m.vx * dt;
m.y += m.vy * dt;
if (m.life > m.ttl || m.y > H + 60) meteors.splice(i, 1);
}
requestAnimationFrame(tick);
}
function draw(dt, ts, scrollY) {
ctx.clearRect(0, 0, W, H);
var t = ts / 1000;
for (var i = 0; i < stars.length; i++) {
var s = stars[i];
var L = LAYERS[s.layer];
if (dt) {
s.y += L.drift * dt;
if (s.y > H + 4) { s.y = -4; s.x = Math.random() * W; }
}
// 视差:滚动让星野整体上移(近处更明显),鼠标让层间错动
var px = s.x + mouseX * L.parallax * 2;
var py = s.y - (scrollY * L.parallax * 0.012) + mouseY * L.parallax;
// 纵向包裹,保证滚动后星点仍铺满
var wrappedY = ((py % (H + 8)) + (H + 8)) % (H + 8) - 4;
var tw = 1 - L.twinkle * 0.5 + L.twinkle * 0.5 * Math.sin(t * s.speed + s.phase) * s.amp;
var a = L.alpha * Math.max(0.15, Math.min(1, tw));
ctx.globalAlpha = a;
ctx.fillStyle = '#dfe6f5';
ctx.fillRect(px, wrappedY, s.r, s.r);
}
// 流星:短细线段,头部略亮(线性渐变线条,不是光晕)
for (var j = 0; j < meteors.length; j++) {
var m = meteors[j];
var fade = 1 - m.life / m.ttl;
var tailX = m.x - m.vx * 0.14;
var tailY = m.y - m.vy * 0.14;
var grad = ctx.createLinearGradient(tailX, tailY, m.x, m.y);
grad.addColorStop(0, 'rgba(223,230,245,0)');
grad.addColorStop(1, 'rgba(223,230,245,' + (0.6 * fade).toFixed(3) + ')');
ctx.globalAlpha = 1;
ctx.strokeStyle = grad;
ctx.lineWidth = 1.1;
ctx.beginPath();
ctx.moveTo(tailX, tailY);
ctx.lineTo(m.x, m.y);
ctx.stroke();
}
ctx.globalAlpha = 1;
}
resize();
window.addEventListener('resize', resize, { passive: true });
if (reduceMotion) {
draw(0, 0, 0); // 静态星图
} else {
scheduleMeteor(performance.now() + 6000); // 首颗稍提前,证明存在
requestAnimationFrame(tick);
// 滚动时重绘由 rAF 持续驱动,无需 scroll 监听
}
/* ═══════════ 2) 顶栏滚动态 ═══════════ */
var topbar = document.getElementById('topbar');
function onScroll() {
topbar.classList.toggle('scrolled', (window.scrollY || 0) > 24);
}
window.addEventListener('scroll', onScroll, { passive: true });
onScroll();
/* ═══════════ 3) FAQ 平滑手风琴 ═══════════ */
var faqItems = document.querySelectorAll('.faq-item');
faqItems.forEach(function (item) {
var summary = item.querySelector('.faq-q');
var panel = item.querySelector('.faq-a');
summary.addEventListener('click', function (e) {
e.preventDefault();
if (reduceMotion) {
item.open = !item.open;
return;
}
if (item.open) {
// 收起:先固定高度再过渡到 0
var h0 = panel.offsetHeight;
panel.style.height = h0 + 'px';
requestAnimationFrame(function () {
panel.style.transition = 'height 0.22s ease';
panel.style.height = '0px';
});
panel.addEventListener('transitionend', function done() {
panel.removeEventListener('transitionend', done);
panel.style.cssText = '';
item.open = false;
});
} else {
item.open = true;
var h1 = panel.offsetHeight;
panel.style.height = '0px';
requestAnimationFrame(function () {
panel.style.transition = 'height 0.24s ease';
panel.style.height = h1 + 'px';
});
panel.addEventListener('transitionend', function done() {
panel.removeEventListener('transitionend', done);
panel.style.cssText = '';
});
}
});
});
/* ═══════════ 4) 演示区等比缩放 ═══════════ */
var frame = document.getElementById('demoFrame');
var iframe = document.getElementById('demoIframe');
var DEMO_W = 1400;
var DEMO_H = 860;
function fitDemo() {
var available = frame.clientWidth;
var scale = Math.min(1, available / DEMO_W);
iframe.style.transform = 'scale(' + scale + ')';
frame.style.height = Math.round(DEMO_H * scale) + 'px';
}
if ('ResizeObserver' in window) {
new ResizeObserver(fitDemo).observe(frame);
}
window.addEventListener('resize', fitDemo, { passive: true });
fitDemo();
/* ═══════════ 5) 品牌眼睛 ═══════════ */
if (window.AstrionAvatar) {
AstrionAvatar.mount(document.getElementById('heroMascot'), { mode: 'idle', blink: true, track: true });
}
/* ═══════════ 6) 运行模式动画(功能 01 行) ═══════════ */
if (window.AnimModes && document.getElementById('animModes')) {
AnimModes.init(document.getElementById('animModes'));
}
/* ═══════════ 7) 沙箱与网络授权动画(功能 02 行) ═══════════ */
if (window.AnimSandbox && document.getElementById('animSandbox')) {
AnimSandbox.init(document.getElementById('animSandbox'));
}
})();