You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 13:17:03