你现在盯着看的这片背景——漂浮的星点、若隐若现的连线、被鼠标推开的粒子——全部由不到 100 行 JavaScript 画成,没有任何第三方库。这篇文章就把它拆开揉碎讲给你。
原理:三件小事
整个"星河"其实只做三件事:
- 在画布上撒一堆随机位置的粒子,让它们缓慢漂移;
- 距离小于阈值的两个粒子之间画一条渐隐的线;
- 鼠标靠近时轻轻推开粒子,制造互动感。
先拿到画布并把它的尺寸铺满视口:
const cv = document.querySelector('#particle-canvas');
const ctx = cv.getContext('2d');
let W, H;
function resize() {
W = cv.width = innerWidth;
H = cv.height = innerHeight;
}
resize();
addEventListener('resize', resize);
第一步:撒星星
粒子的数量按屏幕面积算,大屏多点、小屏少点,避免手机上卡成 PPT。每个粒子记录位置、速度、半径和颜色:
const COLORS = ['0,229,255', '168,85,247', '255,61,129'];
function spawn() {
const n = Math.min(110, Math.round(W * H / 16000));
pts = new Array(n).fill(0).map(() => ({
x: Math.random() * W,
y: Math.random() * H,
vx: (Math.random() - .5) * .35, // 慢速漂移
vy: (Math.random() - .5) * .35,
r: Math.random() * 1.4 + .5,
c: COLORS[Math.random() * COLORS.length | 0]
}));
}
每一帧让粒子按速度移动,飘出边界就绕回另一侧(wrap around),星河就"活"了。
第二步:连线成网
这是灵魂所在:两两计算距离,小于 LINK(比如 130px)就连线,透明度随距离衰减。距离越近,线越亮:
for (let i = 0; i < pts.length; i++) {
for (let j = i + 1; j < pts.length; j++) {
const d = Math.hypot(a.x - b.x, a.y - b.y);
if (d < LINK) {
ctx.strokeStyle = `rgba(77,124,255,${1 - d / LINK})`;
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(b.x, b.y);
ctx.stroke();
}
}
}
鼠标引力
监听 pointermove 记录鼠标位置,粒子在半径 110px 内被轻轻推开——推力与距离成反比。就这一行物理学,互动感直接拉满:
const dx = p.x - mouse.x, dy = p.y - mouse.y;
const d = Math.hypot(dx, dy);
if (d < 110 && d > .01) {
p.x += dx / d * .55; // 单位向量 × 推力
p.y += dy / d * .55;
}
第三步:性能小贴士
- 用
requestAnimationFrame驱动动画,标签页切走时浏览器会自动暂停,不白烧电; - 粒子数封顶。110 个粒子的两两测距约 6000 次/帧,完全没压力;上不封顶才是灾难;
- 透明度预计算进字符串比每帧
globalAlpha来回切换更快; - 尊重
prefers-reduced-motion——对晕动症用户友好,是专业的底线。
最好的特效不是让人惊叹"这用了什么库",而是让人根本意识不到它是特效。
完整的实现就嵌在这个页面的源码里(main.js),右键查看源码即可自取。给你的博客也点一片星河吧。✨