东燊科技(Donsen)宣传网页:中英双语单页站 + 未来科技感视觉系统
- 单页双语站:data-lang 切换中/英,全站内容双语文案 - Hero 不对称双栏:左文案列(eyebrow/标题/渐变 slogan/CTA/数据条)+ 右凤凰视觉舱(深色玻璃 HUD + 能量轨道环 + 均衡器脉冲) - 视觉系统 fx.css/fx.js(与 main.js 解耦):深空星野/鼠标聚光/自定义光标/点击爆炸/卡片 3D 倾斜/数字滚动;?static=1、prefers-reduced-motion、触屏自动降级 - 产品矩阵四段式结构(痛点→方案→场景→效益),四条产品线全部真实截图(WebP + PNG 兜底) - 顶栏/页脚透明凤凰标识(phoenix.webp/png),去白底 - 部署配套:404.html、_headers、nginx.conf、robots.txt、favicon/apple-touch-icon/og-cover - 性能基线:总传输约 153KB、CLS 0、CDP 两套取证套件全绿
This commit is contained in:
+288
@@ -0,0 +1,288 @@
|
||||
/* 东燊科技 Donsen — 未来科技感动效层(fx.js)
|
||||
依赖:fx.css。与 main.js 完全解耦,任一方失效不影响基础功能。
|
||||
模块:① 深空星野 ② 鼠标聚光 ③ 自定义光标 ④ 点击爆炸 ⑤ 卡片 3D 倾斜 ⑥ 数字滚动 */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var html = document.documentElement;
|
||||
var params = new URLSearchParams(location.search);
|
||||
var staticMode = params.get('static') === '1';
|
||||
var reduceMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
// 触屏设备(含无头移动端模拟)不启用自定义光标/聚光/倾斜;爆炸效果保留(pointerdown 兼容触摸)
|
||||
var isTouch = (navigator.maxTouchPoints || 0) > 0 || 'ontouchstart' in window;
|
||||
var finePointer = window.matchMedia && window.matchMedia('(pointer:fine)').matches && !isTouch;
|
||||
var dpr = Math.min(window.devicePixelRatio || 1, 2);
|
||||
|
||||
/* ============ ① 深空星野 ============ */
|
||||
function starfield() {
|
||||
var cv = document.getElementById('starfield');
|
||||
if (!cv) return null;
|
||||
var ctx = cv.getContext('2d');
|
||||
var W = 0, H = 0, stars = [], shooters = [], raf = 0, mx = 0, my = 0, tmx = 0, tmy = 0;
|
||||
|
||||
function resize() {
|
||||
W = window.innerWidth; H = window.innerHeight;
|
||||
cv.width = W * dpr; cv.height = H * dpr;
|
||||
cv.style.width = W + 'px'; cv.style.height = H + 'px';
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
}
|
||||
function seed(done) {
|
||||
var n = Math.round((W * H) / 14000);
|
||||
stars = [];
|
||||
var i = 0;
|
||||
// 分片生成(每帧 40 颗):避免首屏启动时一次循环占满主线程构成长任务
|
||||
function chunk() {
|
||||
var end = Math.min(i + 40, n);
|
||||
for (; i < end; i++) {
|
||||
var depth = 0.25 + Math.random() * 0.75;
|
||||
stars.push({
|
||||
x: Math.random() * W, y: Math.random() * H,
|
||||
r: depth * 1.5 + 0.3,
|
||||
a: 0.25 + Math.random() * 0.65,
|
||||
tw: 0.6 + Math.random() * 1.8, ph: Math.random() * Math.PI * 2,
|
||||
vy: depth * 0.05, hue: Math.random() < 0.12 ? 190 : (Math.random() < 0.1 ? 32 : 215),
|
||||
depth: depth
|
||||
});
|
||||
}
|
||||
if (i < n) { requestAnimationFrame(chunk); return; }
|
||||
if (done) done();
|
||||
}
|
||||
chunk();
|
||||
}
|
||||
function seedAndStart() {
|
||||
stop();
|
||||
seed(function () {
|
||||
if (staticMode || reduceMotion) { frame(0); stop(); } else start();
|
||||
});
|
||||
}
|
||||
function spawnShooter() {
|
||||
if (shooters.length > 1 || Math.random() > 0.35) return;
|
||||
shooters.push({
|
||||
x: Math.random() * W * 0.7, y: Math.random() * H * 0.4,
|
||||
vx: 7 + Math.random() * 5, vy: 2.4 + Math.random() * 1.6, life: 1
|
||||
});
|
||||
}
|
||||
function frame(ms) {
|
||||
var t = ms / 1000;
|
||||
tmx += (mx - tmx) * 0.04; tmy += (my - tmy) * 0.04;
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
for (var i = 0; i < stars.length; i++) {
|
||||
var s = stars[i];
|
||||
var tw = 0.72 + 0.28 * Math.sin(t * s.tw + s.ph);
|
||||
var px = s.x + tmx * 26 * s.depth;
|
||||
var py = s.y + tmy * 18 * s.depth;
|
||||
if (!reduceMotion) { s.y += s.vy; if (s.y > H + 4) { s.y = -4; s.x = Math.random() * W; } }
|
||||
ctx.beginPath();
|
||||
ctx.arc(px, py, s.r * tw, 0, Math.PI * 2);
|
||||
ctx.fillStyle = 'hsla(' + s.hue + ',90%,' + (72 + s.depth * 18) + '%,' + (s.a * tw).toFixed(3) + ')';
|
||||
ctx.fill();
|
||||
if (s.r > 1.35) { // 大星加光晕
|
||||
ctx.beginPath();
|
||||
ctx.arc(px, py, s.r * 3.2, 0, Math.PI * 2);
|
||||
ctx.fillStyle = 'hsla(' + s.hue + ',95%,75%,' + (s.a * 0.10).toFixed(3) + ')';
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
// 流星
|
||||
if (!reduceMotion && Math.random() < 0.0022) spawnShooter();
|
||||
for (var j = shooters.length - 1; j >= 0; j--) {
|
||||
var sh = shooters[j];
|
||||
sh.x += sh.vx; sh.y += sh.vy; sh.life -= 0.012;
|
||||
var g = ctx.createLinearGradient(sh.x, sh.y, sh.x - sh.vx * 9, sh.y - sh.vy * 9);
|
||||
g.addColorStop(0, 'rgba(255,255,255,' + (0.85 * sh.life).toFixed(3) + ')');
|
||||
g.addColorStop(1, 'rgba(255,255,255,0)');
|
||||
ctx.strokeStyle = g; ctx.lineWidth = 1.6;
|
||||
ctx.beginPath(); ctx.moveTo(sh.x, sh.y); ctx.lineTo(sh.x - sh.vx * 9, sh.y - sh.vy * 9); ctx.stroke();
|
||||
if (sh.life <= 0 || sh.x > W + 60) shooters.splice(j, 1);
|
||||
}
|
||||
if (!document.hidden) raf = requestAnimationFrame(frame);
|
||||
}
|
||||
function start() { if (!raf && !document.hidden) raf = requestAnimationFrame(frame); }
|
||||
function stop() { if (raf) { cancelAnimationFrame(raf); raf = 0; } }
|
||||
document.addEventListener('visibilitychange', function () { document.hidden ? stop() : start(); });
|
||||
|
||||
window.addEventListener('resize', function () { resize(); seedAndStart(); });
|
||||
window.addEventListener('mousemove', function (e) {
|
||||
mx = (e.clientX / W - 0.5) * 2; my = (e.clientY / H - 0.5) * 2;
|
||||
});
|
||||
resize(); seedAndStart();
|
||||
return { stop: stop, start: start };
|
||||
}
|
||||
|
||||
/* ============ ② 鼠标聚光 + ③ 自定义光标 ============ */
|
||||
function pointerFx() {
|
||||
if (staticMode || reduceMotion || !finePointer) return null;
|
||||
html.classList.add('fx-on');
|
||||
var spot = document.getElementById('spotlight');
|
||||
var dot = document.querySelector('.cursor-dot');
|
||||
var ring = document.querySelector('.cursor-ring');
|
||||
var tx = innerWidth / 2, ty = innerHeight / 2, sx = tx, sy = ty, rx = tx, ry = ty, raf = 0;
|
||||
|
||||
function loop() {
|
||||
sx += (tx - sx) * 0.12; sy += (ty - sy) * 0.12;
|
||||
rx += (tx - rx) * 0.24; ry += (ty - ry) * 0.24;
|
||||
if (spot) spot.style.transform = 'translate3d(' + sx + 'px,' + sy + 'px,0)';
|
||||
if (dot) dot.style.transform = 'translate3d(' + tx + 'px,' + ty + 'px,0)';
|
||||
if (ring) ring.style.transform = 'translate3d(' + rx + 'px,' + ry + 'px,0)';
|
||||
// 三轴全部收敛到指针(<0.5px)即休眠,下次 mousemove 重启——避免指针静止后 rAF 永不停转
|
||||
if (Math.abs(tx - sx) < 0.5 && Math.abs(ty - sy) < 0.5 &&
|
||||
Math.abs(tx - rx) < 0.5 && Math.abs(ty - ry) < 0.5) {
|
||||
raf = 0;
|
||||
return;
|
||||
}
|
||||
raf = requestAnimationFrame(loop);
|
||||
}
|
||||
window.addEventListener('mousemove', function (e) {
|
||||
tx = e.clientX; ty = e.clientY;
|
||||
if (!raf) raf = requestAnimationFrame(loop);
|
||||
}, { passive: true });
|
||||
if (!raf) raf = requestAnimationFrame(loop);
|
||||
|
||||
// 悬停可交互元素:光环放大
|
||||
var hot = 'a,button,[data-tilt],.scene-chips span,.benefit-item';
|
||||
document.addEventListener('mouseover', function (e) {
|
||||
if (e.target.closest && e.target.closest(hot) && ring) ring.classList.add('is-hot');
|
||||
});
|
||||
document.addEventListener('mouseout', function (e) {
|
||||
if (e.target.closest && e.target.closest(hot) && ring) ring.classList.remove('is-hot');
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ============ ④ 点击爆炸 ============ */
|
||||
function explosion() {
|
||||
var layer = document.getElementById('fx-layer');
|
||||
if (!layer) return null;
|
||||
var ctx = layer.getContext('2d');
|
||||
var parts = [], rings = [], raf = 0;
|
||||
var COLORS = ['#5B9BFF', '#2E7CF6', '#5BE9FF', '#FFFFFF', '#9D8CFF'];
|
||||
|
||||
function resize() {
|
||||
layer.width = innerWidth * dpr; layer.height = innerHeight * dpr;
|
||||
layer.style.width = innerWidth + 'px'; layer.style.height = innerHeight + 'px';
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
}
|
||||
window.addEventListener('resize', resize); resize();
|
||||
|
||||
function burst(x, y, warm) {
|
||||
if (staticMode || reduceMotion) return;
|
||||
var n = 34 + Math.round(Math.random() * 10);
|
||||
for (var i = 0; i < n; i++) {
|
||||
var ang = Math.random() * Math.PI * 2;
|
||||
var sp = 1.6 + Math.random() * 7.2;
|
||||
parts.push({
|
||||
x: x, y: y,
|
||||
vx: Math.cos(ang) * sp, vy: Math.sin(ang) * sp - 1.4,
|
||||
r: 1.1 + Math.random() * 2.6,
|
||||
life: 1, decay: 0.016 + Math.random() * 0.02,
|
||||
col: warm && Math.random() < 0.4 ? '#FFB37A' : COLORS[(Math.random() * COLORS.length) | 0]
|
||||
});
|
||||
}
|
||||
rings.push({ x: x, y: y, r: 6, life: 1, warm: !!warm });
|
||||
var el = document.createElement('div');
|
||||
el.className = 'shockwave' + (warm ? ' warm' : '');
|
||||
el.style.left = x + 'px'; el.style.top = y + 'px';
|
||||
document.body.appendChild(el);
|
||||
setTimeout(function () { el.remove(); }, 700);
|
||||
if (!raf) raf = requestAnimationFrame(frame);
|
||||
}
|
||||
function frame() {
|
||||
ctx.clearRect(0, 0, innerWidth, innerHeight);
|
||||
var i, p;
|
||||
for (i = parts.length - 1; i >= 0; i--) {
|
||||
p = parts[i];
|
||||
p.x += p.vx; p.y += p.vy; p.vy += 0.14; p.vx *= 0.985; p.vy *= 0.985;
|
||||
p.life -= p.decay;
|
||||
if (p.life <= 0) { parts.splice(i, 1); continue; }
|
||||
ctx.globalAlpha = Math.max(0, p.life);
|
||||
ctx.fillStyle = p.col;
|
||||
ctx.beginPath(); ctx.arc(p.x, p.y, p.r * p.life + 0.4, 0, Math.PI * 2); ctx.fill();
|
||||
}
|
||||
for (i = rings.length - 1; i >= 0; i--) {
|
||||
var r = rings[i];
|
||||
r.r += 7.5; r.life -= 0.045;
|
||||
if (r.life <= 0) { rings.splice(i, 1); continue; }
|
||||
ctx.globalAlpha = Math.max(0, r.life * 0.8);
|
||||
ctx.strokeStyle = r.warm ? '#FFB37A' : '#5BE9FF';
|
||||
ctx.lineWidth = 2 * r.life;
|
||||
ctx.beginPath(); ctx.arc(r.x, r.y, r.r, 0, Math.PI * 2); ctx.stroke();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
raf = (parts.length || rings.length) ? requestAnimationFrame(frame) : 0;
|
||||
}
|
||||
window.addEventListener('pointerdown', function (e) {
|
||||
burst(e.clientX, e.clientY, Math.random() < 0.16);
|
||||
});
|
||||
return { burst: burst };
|
||||
}
|
||||
|
||||
/* ============ ⑤ 卡片 3D 倾斜 + 指针辉光 ============ */
|
||||
function tilt() {
|
||||
if (staticMode || reduceMotion || !finePointer) return null;
|
||||
var els = document.querySelectorAll('[data-tilt]');
|
||||
// 矩形缓存:mouseenter 时读一次,mousemove 直接用;滚动/缩放/离开时失效
|
||||
// (15 张卡 × 每秒数十次 mousemove,每次都 getBoundingClientRect 会强制布局)
|
||||
function invalidate() {
|
||||
for (var i = 0; i < els.length; i++) els[i]._rect = null;
|
||||
}
|
||||
els.forEach(function (el) {
|
||||
el.addEventListener('mouseenter', function () {
|
||||
el._rect = el.getBoundingClientRect();
|
||||
});
|
||||
el.addEventListener('mousemove', function (e) {
|
||||
var r = el._rect;
|
||||
if (!r) r = el._rect = el.getBoundingClientRect();
|
||||
var px = (e.clientX - r.left) / r.width, py = (e.clientY - r.top) / r.height;
|
||||
el.style.setProperty('--mx', (px * 100).toFixed(1) + '%');
|
||||
el.style.setProperty('--my', (py * 100).toFixed(1) + '%');
|
||||
var rx = (0.5 - py) * 5, ry = (px - 0.5) * 5;
|
||||
el.style.transform = 'perspective(900px) rotateX(' + rx.toFixed(2) + 'deg) rotateY(' + ry.toFixed(2) + 'deg) translateY(-4px)';
|
||||
});
|
||||
el.addEventListener('mouseleave', function () {
|
||||
el._rect = null;
|
||||
el.style.transform = 'perspective(900px) rotateX(0deg) rotateY(0deg)';
|
||||
});
|
||||
});
|
||||
window.addEventListener('scroll', invalidate, { passive: true });
|
||||
window.addEventListener('resize', invalidate);
|
||||
return els.length;
|
||||
}
|
||||
|
||||
/* ============ ⑥ 数字滚动 ============ */
|
||||
function countUp() {
|
||||
var els = Array.prototype.slice.call(document.querySelectorAll('[data-count]'));
|
||||
if (!els.length) return 0;
|
||||
function setFinal(el) { el.textContent = el.getAttribute('data-count') + (el.getAttribute('data-suffix') || ''); }
|
||||
if (staticMode || reduceMotion || !('IntersectionObserver' in window)) {
|
||||
els.forEach(setFinal); return els.length;
|
||||
}
|
||||
var io = new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (en) {
|
||||
if (!en.isIntersecting) return;
|
||||
var el = en.target, end = parseFloat(el.getAttribute('data-count')) || 0;
|
||||
var suffix = el.getAttribute('data-suffix') || '';
|
||||
var t0 = performance.now(), dur = 1100;
|
||||
(function step(now) {
|
||||
var k = Math.min(1, (now - t0) / dur);
|
||||
var eased = 1 - Math.pow(1 - k, 3);
|
||||
el.textContent = Math.round(end * eased) + suffix;
|
||||
if (k < 1) requestAnimationFrame(step); else el.textContent = end + suffix;
|
||||
})(t0);
|
||||
io.unobserve(el);
|
||||
});
|
||||
}, { threshold: 0.4 });
|
||||
els.forEach(function (el) { io.observe(el); });
|
||||
return els.length;
|
||||
}
|
||||
|
||||
/* ============ 启动 ============ */
|
||||
function boot() {
|
||||
starfield();
|
||||
pointerFx();
|
||||
explosion();
|
||||
tilt();
|
||||
countUp();
|
||||
}
|
||||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot);
|
||||
else boot();
|
||||
})();
|
||||
@@ -0,0 +1,102 @@
|
||||
/* 东燊科技 Donsen — 交互脚本 */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
/* ---------- 语言切换(默认中文,记忆选择) ---------- */
|
||||
var body = document.body;
|
||||
var toggle = document.getElementById('langToggle');
|
||||
var optZh = document.querySelector('.lang-zh');
|
||||
var optEn = document.querySelector('.lang-en');
|
||||
|
||||
function applyLang(lang) {
|
||||
body.setAttribute('data-lang', lang);
|
||||
document.documentElement.setAttribute('lang', lang === 'zh' ? 'zh-CN' : 'en');
|
||||
if (optZh) optZh.classList.toggle('is-active', lang === 'zh');
|
||||
if (optEn) optEn.classList.toggle('is-active', lang === 'en');
|
||||
try { localStorage.setItem('donsen-lang', lang); } catch (e) {}
|
||||
}
|
||||
|
||||
var params = new URLSearchParams(location.search);
|
||||
var saved = null;
|
||||
try { saved = params.get('lang') || localStorage.getItem('donsen-lang'); } catch (e) {}
|
||||
applyLang(saved === 'en' ? 'en' : 'zh');
|
||||
|
||||
if (toggle) {
|
||||
toggle.addEventListener('click', function () {
|
||||
applyLang(body.getAttribute('data-lang') === 'zh' ? 'en' : 'zh');
|
||||
});
|
||||
}
|
||||
|
||||
/* ---------- 移动端菜单 ---------- */
|
||||
var burger = document.getElementById('navBurger');
|
||||
var links = document.getElementById('navLinks');
|
||||
if (burger && links) {
|
||||
burger.addEventListener('click', function () {
|
||||
links.classList.toggle('open');
|
||||
});
|
||||
links.querySelectorAll('a').forEach(function (a) {
|
||||
a.addEventListener('click', function () { links.classList.remove('open'); });
|
||||
});
|
||||
}
|
||||
|
||||
/* ---------- 滚动高亮导航(rAF 节流 + offsetTop 缓存,滚动帧内零强制布局) ---------- */
|
||||
var navEl = document.getElementById('nav');
|
||||
var sections = Array.prototype.slice.call(document.querySelectorAll('section[id]'));
|
||||
var navLinks = Array.prototype.slice.call(document.querySelectorAll('.nav-link'));
|
||||
var sectionTops = [];
|
||||
function measureTops() {
|
||||
sectionTops = sections.map(function (s) { return s.getBoundingClientRect().top + window.scrollY; });
|
||||
}
|
||||
var scrollTick = false;
|
||||
function onScrollFrame() {
|
||||
scrollTick = false;
|
||||
var y = window.scrollY;
|
||||
var scrolled = y > 40;
|
||||
if (navEl) {
|
||||
navEl.style.background = scrolled ? 'rgba(6,15,34,.96)' : 'rgba(6,15,34,.82)';
|
||||
// 滚动后切纯色背景并去掉毛玻璃(backdrop-filter 持续混合开销大)
|
||||
navEl.classList.toggle('scrolled', scrolled);
|
||||
}
|
||||
var current = sections[0] ? sections[0].id : '';
|
||||
for (var i = 0; i < sectionTops.length; i++) {
|
||||
if (sectionTops[i] <= y + 120) current = sections[i].id;
|
||||
}
|
||||
navLinks.forEach(function (l) {
|
||||
l.classList.toggle('active', l.getAttribute('href') === '#' + current);
|
||||
});
|
||||
}
|
||||
function onScroll() {
|
||||
if (scrollTick) return;
|
||||
scrollTick = true;
|
||||
requestAnimationFrame(onScrollFrame);
|
||||
}
|
||||
measureTops();
|
||||
window.addEventListener('scroll', onScroll, { passive: true });
|
||||
window.addEventListener('resize', function () { measureTops(); onScrollFrame(); });
|
||||
onScrollFrame();
|
||||
|
||||
/* ---------- 滚动显现动画(?static=1 或减弱动效时直接全展) ---------- */
|
||||
var revealEls = document.querySelectorAll('.reveal');
|
||||
var staticMode = new URLSearchParams(location.search).get('static') === '1';
|
||||
var reduceMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
if (staticMode || reduceMotion || !('IntersectionObserver' in window)) {
|
||||
revealEls.forEach(function (el) { el.classList.add('in'); });
|
||||
} else {
|
||||
var io = new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (en) {
|
||||
if (en.isIntersecting) {
|
||||
en.target.classList.add('in');
|
||||
io.unobserve(en.target);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.12 });
|
||||
revealEls.forEach(function (el) { io.observe(el); });
|
||||
}
|
||||
|
||||
/* ---------- 页脚年份 ---------- */
|
||||
var y = new Date().getFullYear();
|
||||
['year', 'year-en'].forEach(function (id) {
|
||||
var el = document.getElementById(id);
|
||||
if (el) el.textContent = y;
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user