如何让Canvas粒子沿螺旋轨迹往返移动?
实现粒子螺旋往返的像素雨效果
需求说明
原本的像素雨效果中,粒子从左向右移动,速度随对应位置图像亮度变化,颜色取自图像像素。现在需要调整为:粒子从画布边缘螺旋移动至中心,到达中心后再螺旋返回边缘,保持速度随亮度变化、颜色取自像素的特性。
修改后的完整代码
JavaScript 代码
const myImage = new Image(); myImage.src = '替换为你的Base64图片内容'; myImage.addEventListener('load', function() { const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); canvas.width = 600; canvas.height = 714; ctx.drawImage(myImage, 0, 0, canvas.width, canvas.height); const pixels = ctx.getImageData(0, 0, canvas.width, canvas.height); let particlesArray = []; const ParticlesAmount = 7000; let mappedImage = []; const centerX = canvas.width / 2; const centerY = canvas.height / 2; // 映射图像像素的亮度和颜色 for (let y = 0; y < canvas.height; y++) { let row = []; for (let x = 0; x < canvas.width; x++) { const red = pixels.data[(y * 4 * pixels.width) + (x * 4)]; const green = pixels.data[(y * 4 * pixels.width) + (x * 4 + 1)]; const blue = pixels.data[(y * 4 * pixels.width) + (x * 4 + 2)]; const alpha = pixels.data[(y * 4 * pixels.width) + (x * 4 + 3)]; const brightness = calculateRelativeBrightness(red, green, blue); const cell = [brightness, `rgba(${red}, ${green}, ${blue}, ${alpha})`]; row.push(cell); } mappedImage.push(row); } // 计算人眼感知的相对亮度 function calculateRelativeBrightness(red, green, blue) { return Math.sqrt( (red * red) * 0.299 + (green * green) * 0.587 + (blue * blue) * 0.114 ); } class Particle { constructor() { // 初始化在画布边缘的随机位置 const angle = Math.random() * Math.PI * 2; const maxRadius = Math.sqrt(centerX**2 + centerY**2); this.radius = maxRadius; this.angle = angle; this.x = centerX + this.radius * Math.cos(angle); this.y = centerY + this.radius * Math.sin(angle); this.speed = 0; this.baseVelocity = Math.random() * 0.3 + 0.1; // 基础螺旋速度 this.size = Math.random() * 1.5 + 1; this.moveDirection = 'in'; // 初始向内移动 } update() { // 更新当前像素位置 this.positionX = Math.floor(this.x); this.positionY = Math.floor(this.y); // 获取对应位置的亮度,调整移动速度 if (this.positionY >= 0 && this.positionY < mappedImage.length && this.positionX >= 0 && this.positionX < mappedImage[0].length) { this.speed = mappedImage[this.positionY][this.positionX][0]; // 亮度越低,移动越快(保持原逻辑) const adjustedSpeed = (2.5 - this.speed / 25) * this.baseVelocity; // 处理螺旋移动逻辑 if (this.moveDirection === 'in') { this.radius -= adjustedSpeed; // 到达中心附近,切换方向 if (this.radius <= 5) { this.moveDirection = 'out'; } } else { this.radius += adjustedSpeed; const maxRadius = Math.sqrt(centerX**2 + centerY**2); // 到达边缘,切换方向 if (this.radius >= maxRadius) { this.moveDirection = 'in'; } } // 更新角度,实现螺旋效果 this.angle += adjustedSpeed * 0.02; this.x = centerX + this.radius * Math.cos(this.angle); this.y = centerY + this.radius * Math.sin(this.angle); } else { // 超出画布时,重置到边缘 const angle = Math.random() * Math.PI * 2; const maxRadius = Math.sqrt(centerX**2 + centerY**2); this.radius = maxRadius; this.angle = angle; this.x = centerX + this.radius * Math.cos(angle); this.y = centerY + this.radius * Math.sin(angle); this.moveDirection = 'in'; } } draw() { ctx.beginPath(); if (this.positionY >= 0 && this.positionY < mappedImage.length && this.positionX >= 0 && this.positionX < mappedImage[0].length) { ctx.fillStyle = mappedImage[this.positionY][this.positionX][1]; } else { ctx.fillStyle = 'white'; } ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fill(); } } function init() { particlesArray = []; for (let i = 0; i < ParticlesAmount; i++) { particlesArray.push(new Particle()); } } init(); function animate() { ctx.globalAlpha = 0.05; ctx.fillStyle = 'rgb(0, 0, 0)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.globalAlpha = 1; // 重置透明度,避免粒子颜色变淡 for (let i = 0; i < particlesArray.length; i++) { particlesArray[i].update(); particlesArray[i].draw(); } requestAnimationFrame(animate); } animate(); })
CSS 代码
* { margin: 0; padding: 0; box-sizing: border-box; } body { background-color: #222; } #canvas { border: 2px solid #f5f5f5; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 600px; height: 714px; }
HTML 代码
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="style.css"> <title>螺旋像素雨</title> </head> <body> <canvas id="canvas"></canvas> <script src="script.js"></script> </body> </html>
关键修改说明
- 粒子初始化:将粒子初始位置设置在画布边缘,通过极坐标计算边缘点(中心+最大半径×三角函数)
- 螺旋运动逻辑:给粒子添加
radius(到中心的距离)、angle(角度)、moveDirection(移动方向)属性,通过增减radius实现往返,同时递增angle形成螺旋轨迹 - 速度关联亮度:保留原逻辑,亮度越低粒子移动越快,将亮度值映射到螺旋移动的速度上
- 边界处理:粒子到达中心或边缘时切换移动方向,超出画布时重置位置到边缘
内容的提问来源于stack exchange,提问作者Mr Joseph Charles
相关产品推荐
相关产品推荐

