代码如下
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Wave Animation</title>
<style>
body {
margin: 0;
overflow: hidden;
}
canvas {
display: block;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
class Wave {
constructor(e) {
this.phase = e.phase || 0;
this.offset = e.offset || 0;
this.frequency = e.frequency || 0.001;
this.amplitude = e.amplitude || 1;
}
update() {
this.phase += this.frequency;
return this.offset + Math.sin(this.phase) * this.amplitude;
}
}
class Node {
constructor() {
this.x = 0;
this.y = 0;
this.vy = 0;
this.vx = 0;
}
}
class Line {
constructor(e, pos) {
this.spring = e.spring + 0.1 * Math.random() - 0.05;
this.friction = E.friction + 0.01 * Math.random() - 0.005;
this.nodes = [];
this.pos = pos;
for (let i = 0; i < E.size; i++) {
const t = new Node();
t.x = this.pos.x;
t.y = this.pos.y;
this.nodes.push(t);
}
}
update() {
let spring = this.spring;
let node = this.nodes[0];
node.vx += (this.pos.x - node.x) * spring;
node.vy += (this.pos.y - node.y) * spring;
let prevNode;
for (let i = 0; i < this.nodes.length; i++) {
node = this.nodes[i];
if (i > 0) {
prevNode = this.nodes[i - 1];
node.vx += (prevNode.x - node.x) * spring;
node.vy += (prevNode.y - node.y) * spring;
node.vx += prevNode.vx * E.dampening;
node.vy += prevNode.vy * E.dampening;
}
node.vx *= this.friction;
node.vy *= this.friction;
node.x += node.vx;
node.y += node.vy;
spring *= E.tension;
}
}
draw(ctx) {
let currNode,
nextNode,
x = this.nodes[0].x,
y = this.nodes[0].y;
ctx.beginPath();
ctx.moveTo(x, y);
let i;
for (i = 1; i < this.nodes.length - 2; i++) {
currNode = this.nodes[i];
nextNode = this.nodes[i + 1];
x = 0.5 * (currNode.x + nextNode.x);
y = 0.5 * (currNode.y + nextNode.y);
ctx.quadraticCurveTo(currNode.x, currNode.y, x, y);
}
currNode = this.nodes[i];
nextNode = this.nodes[i + 1];
ctx.quadraticCurveTo(currNode.x, currNode.y, nextNode.x, nextNode.y);
ctx.stroke();
ctx.closePath();
}
}
const E = {
friction: 0.5,
trails: 20,
size: 50,
dampening: 0.25,
tension: 0.98,
};
const renderCanvas = function () {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
let lines = [];
const pos = { x: 0, y: 0 };
const wave = new Wave({
phase: Math.random() * 2 * Math.PI,
amplitude: 85,
frequency: 0.0015,
offset: 285,
});
let running = true;
let frame = 1;
function resizeCanvas() {
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
}
resizeCanvas();
function animate() {
if (running) {
ctx.globalCompositeOperation = "source-over";
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.globalCompositeOperation = "lighter";
ctx.strokeStyle = `hsla(${Math.round(wave.update())}, 90%, 50%, 0.25)`;
ctx.lineWidth = 1;
for (let i = 0; i < E.trails; i++) {
const line = lines[i];
line.update();
line.draw(ctx);
}
frame++;
window.requestAnimationFrame(animate);
}
}
function bindMouseMove(event) {
function drawLine() {
lines = [];
for (let i = 0; i < E.trails; i++)
lines.push(new Line({ spring: 0.45 + (i / E.trails) * 0.025 }, pos));
}
function move(e) {
e.touches
? ((pos.x = e.touches[0].pageX), (pos.y = e.touches[0].pageY))
: ((pos.x = e.clientX), (pos.y = e.clientY));
e.preventDefault();
}
function start(e) {
if (e.touches.length === 1) {
pos.x = e.touches[0].pageX;
pos.y = e.touches[0].pageY;
}
}
document.removeEventListener("mousemove", bindMouseMove);
document.removeEventListener("touchstart", bindMouseMove);
document.addEventListener("mousemove", move);
document.addEventListener("touchmove", move);
document.addEventListener("touchstart", start);
move(event);
drawLine();
animate();
}
document.addEventListener("mousemove", bindMouseMove);
document.addEventListener("touchstart", bindMouseMove);
document.body.addEventListener("orientationchange", resizeCanvas);
window.addEventListener("resize", resizeCanvas);
window.addEventListener("focus", () => {
if (!running) {
running = true;
animate();
}
});
window.addEventListener("blur", () => {
running = true;
});
};
renderCanvas();
</script>
</body>
</html>
#解析HTML代码
HTML结构:
- 页面中包含一个
<canvas>
元素,用于绘制动画。 - CSS样式用于隐藏页面的默认边距,并使画布全屏显示。
JavaScript代码:
- 包含了之前定义的所有类和函数。
renderCanvas
函数被调用以启动动画。
事件监听:
- 添加了鼠标移动和触摸事件监听器,以更新线条的位置。
- 窗口调整大小事件监听器用于保持画布与窗口大小同步。
将上述HTML代码保存为一个.html
文件,并在浏览器中打开,你就可以看到一个随着鼠标移动变化的波浪线动画了。
#解析JS代码
Wave 类:
- 用于描述一个正弦波,包含相位(phase)、偏移(offset)、频率(frequency)和振幅(amplitude)属性。
update
方法用于更新波形,每次调用时相位增加一定的频率,并返回当前波形的位置。
Node 类:
- 代表动画中的一个点,拥有位置(x, y)和速度(vx, vy)。
Line 类:
- 描述由多个
Node
组成的线段。 - 包含弹簧系数(spring)、摩擦系数(friction)和节点列表(nodes)。
update
方法用于更新每个节点的位置,根据相邻节点的位置和速度以及弹簧和摩擦力。draw
方法用于在Canvas上下文上绘制线条。
E 对象:
- 定义了动画的一些常量,如摩擦系数、轨迹数量、节点数量、阻尼系数和张力系数。
renderCanvas 函数:
- 初始化Canvas,并设置其尺寸。
- 创建一个
Wave
实例,并定义了一些动画相关的变量。 animate
函数负责动画的绘制和更新。- 通过监听鼠标移动和触摸事件来更新线条的位置,并开始动画循环。