用 Canvas 打造网页微交互动效:从风铃线条到粒子系统
前言
在追求用户体验的今天,微交互已经不再是"可有可无"的装饰——它们是产品性格的一部分。当用户的鼠标划过页面,看到线条轻轻摇曳、粒子缓缓漂浮,这种细腻的反馈会让整个网站"活"起来。
本文将分享我在个人博客中实现 Canvas 微交互动效的完整思路与技术细节。
为什么选择 Canvas?
相比 CSS Animation 和 SVG,Canvas 在以下场景更具优势:
核心架构:useCanvas Hook
export type DrawCallback = (
ctx: CanvasRenderingContext2D,
dt: number, // 帧间隔(秒)
dims: CanvasDimensions,
) => void;
export function useCanvas(
canvasRef: RefObject<HTMLCanvasElement>,
draw: DrawCallback,
) {
// 处理 DPR 缩放
// ResizeObserver 自适应
// Visibility API 页面隐藏时暂停
// rAF 循环 + dt 时间增量
}
将"画布管理"和"绘制逻辑"分离是关键设计。`draw` 回调只需关心"画什么",不用管缩放、循环、暂停等基础设施。
实战:风铃线条
风铃线条的核心是 **二次贝塞尔曲线** + **鼠标推力**:
// 每条线从顶部锚点垂下
const anchorX = line.anchorX * width;
const endX = anchorX + swayOffset;
const endY = line.endY * height;
// 控制点决定曲线弯曲程度
const cpX = anchorX + line.cpOffsetX * width + swayOffset * 0.7;
const cpY = line.cpOffsetY * height;
ctx.quadraticCurveTo(cpX, cpY, endX, endY);
### 鼠标交互
当鼠标靠近某条线时,计算推力方向和强度:
const dist = Math.abs(mouseX - anchorX);
const maxDist = width * 0.3;
if (dist < maxDist) {
const strength = 1 - dist / maxDist;
mouseInfluence = direction * strength * 20;
}
配合弹性阻尼实现平滑过渡:
line.currentSwayX += (target - line.currentSwayX) * Math.min(1, dt * 3);
### 空闲摇摆
没有鼠标交互时,用正弦波模拟微风:
const idleSway = Math.sin(time * line.idleFreq + line.phaseOffset) * line.idleAmp;
每条线的 `phaseOffset` 和 `idleFreq` 不同,形成自然的错落感。
实战:萤火虫粒子
萤火虫系统更简单——每个粒子是一个发光的圆:
// 呼吸效果:alpha 周期性变化
const alpha = 0.3 + 0.5 * Math.sin(time * particle.pulseFreq + particle.phase);
// 渐变发光
const gradient = ctx.createRadialGradient(x, y, 0, x, y, radius * 3);
gradient.addColorStop(0, `rgba(${rgb}, ${alpha})`);
gradient.addColorStop(1, `rgba(${rgb}, 0)`);
粒子运动用简单的漂移 + 随机扰动:
particle.x += particle.vx * dt;
particle.y += particle.vy * dt;
// 边界反弹
if (particle.x < 0 || particle.x > width) particle.vx *= -1;
性能优化要点
2. **DPR 处理**:`canvas.width = displayWidth * dpr`,然后 `ctx.scale(dpr, dpr)`
3. **Visibility API**:页面不可见时暂停 rAF
4. **条件渲染**:`prefers-reduced-motion` 媒体查询时跳过动画
5. **dt 限制**:`Math.min(dt, 0.1)` 防止切tab后的帧暴涨
最终效果
将多个系统组合:风铃线条作为主背景,萤火虫漂浮其间,再加上秋叶飘落——三层叠加形成丰富而不喧宾夺主的视觉层次。
// HeroCanvasLayer 中组合多个系统
windChimes.draw(ctx, dt, dims); // 底层:线条
fireflies.draw(ctx, dt, dims); // 中层:萤火虫
fallingLeaves.draw(ctx, dt, dims); // 顶层:落叶
总结
Canvas 微交互的核心哲学是 **克制**:
希望这篇文章能给你一些灵感。完整代码可以在我的 GitHub 找到。