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

求助:如何将Canvas粒子动画从文字驱动改为图片驱动?

嘿,很高兴能帮到你这个Canvas粒子动画的小改造!你现在想把基于文字生成粒子的逻辑换成图片,其实核心思路和原来的文字方案是通的——都是先把目标内容(这次是图片)画到Canvas上,然后提取像素点生成粒子。我给你一步步讲怎么改:

核心修改思路

原来的代码是先把输入框的文字绘制到Canvas,再通过getImageData获取像素数据,筛选出不透明的像素点来创建粒子。换成图片的话,只需要把“绘制文字”这一步换成“绘制图片”,剩下的粒子生成、动画逻辑几乎不用动。

具体修改步骤
  • 第一步:替换输入框为图片元素
    把原来的<input>标签换成<img>标签,或者在JS里动态加载图片。比如在HTML里加:

    <img id="sourceImg" src="你的图片路径" style="display:none;" />
    

    这里加display:none是因为我们只需要用它的像素数据,不用显示原图。

  • 第二步:调整initScene函数,处理图片加载
    图片加载是异步的,所以要确保图片加载完成后再处理像素。修改initScene函数,先绘制图片再获取像素:

    function initScene(){
      ww = canvas.width = window.innerWidth;
      wh = canvas.height = window.innerHeight;
      ctx.clearRect(0, 0, canvas.width, canvas.height);
    
      // 替换原来的文字绘制,改成绘制图片
      const img = document.querySelector("#sourceImg");
      // 计算图片居中绘制的位置(可选,也可以按比例缩放)
      const scale = Math.min(ww / img.width, wh / img.height);
      const drawWidth = img.width * scale;
      const drawHeight = img.height * scale;
      const x = (ww - drawWidth) / 2;
      const y = (wh - drawHeight) / 2;
      ctx.drawImage(img, x, y, drawWidth, drawHeight);
    
      // 下面的像素提取和粒子生成逻辑和原来一样
      var data = ctx.getImageData(0, 0, ww, wh).data;
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.globalCompositeOperation = "screen";
      particles = [];
      for(var i=0;i<ww;i+=Math.round(ww/150)){
        for(var j=0;j<wh;j+=Math.round(ww/150)){
          if(data[ ((i + j*ww)*4) + 3] > 150){
            particles.push(new Particle(i,j));
          }
        }
      }
      amount = particles.length;
    }
    
  • 第三步:监听图片加载完成事件
    因为图片加载需要时间,要在图片加载完成后再调用initScene,不然会拿不到像素数据:

    const img = document.querySelector("#sourceImg");
    img.addEventListener("load", function() {
      initScene();
      requestAnimationFrame(render);
    });
    
    // 同时保留窗口 resize 的监听
    window.addEventListener("resize", initScene);
    
完整修改后的代码示例

把所有改动整合起来,完整代码如下:

<!DOCTYPE html>
<html>
<head>
  <style>
    canvas{ background: black; width: 100vw; height: 100vh; }
    #sourceImg { display: none; }
  </style>
</head>
<body>
<canvas id="scene"></canvas>
<img id="sourceImg" src="your-image-path.jpg" />

<script>
var canvas = document.querySelector("#scene"), ctx = canvas.getContext("2d"), particles = [], amount = 0, mouse = {x:0,y:0}, radius = 1;
var colors = ["rgba(255,255,255,1)","rgba(255,255,255,.5)", "rgba(255,255,255,.25)","rgba(255,255,255,.1)"];
var ww = canvas.width = window.innerWidth;
var wh = canvas.height = window.innerHeight;

function Particle(x,y){
 this.x = Math.random()*ww;
 this.y = Math.random()*wh;
 this.dest = { x : x, y: y };
 this.r = Math.random()*5 + 2;
 this.vx = (Math.random()-0.5)*20;
 this.vy = (Math.random()-0.5)*20;
 this.accX = 0;
 this.accY = 0;
 this.friction = Math.random()*0.05 + 0.94;
 this.color = colors[Math.floor(Math.random()*colors.length)]; // 修复原代码数组越界问题
}

Particle.prototype.render = function() {
 this.accX = (this.dest.x - this.x)/1000;
 this.accY = (this.dest.y - this.y)/1000;
 this.vx += this.accX;
 this.vy += this.accY;
 this.vx *= this.friction;
 this.vy *= this.friction;
 this.x += this.vx;
 this.y += this.vy;
 ctx.fillStyle = this.color;
 ctx.beginPath();
 ctx.arc(this.x, this.y, this.r, Math.PI * 2, false);
 ctx.fill();
 var a = this.x - mouse.x;
 var b = this.y - mouse.y;
 var distance = Math.sqrt( a*a + b*b );
 if(distance<(radius*70)){
 this.accX = (this.x - mouse.x)/100;
 this.accY = (this.y - mouse.y)/100;
 this.vx += this.accX;
 this.vy += this.accY;
 }
}

function onMouseMove(e){
 mouse.x = e.clientX;
 mouse.y = e.clientY;
}

function onTouchMove(e){
 if(e.touches.length > 0 ){
 mouse.x = e.touches[0].clientX;
 mouse.y = e.touches[0].clientY;
 }
}

function onTouchEnd(e){
 mouse.x = -9999;
 mouse.y = -9999;
}

function initScene(){
 ww = canvas.width = window.innerWidth;
 wh = canvas.height = window.innerHeight;
 ctx.clearRect(0, 0, canvas.width, canvas.height);

 const img = document.querySelector("#sourceImg");
 // 让图片自适应Canvas,保持比例居中
 const scale = Math.min(ww / img.width, wh / img.height);
 const drawWidth = img.width * scale;
 const drawHeight = img.height * scale;
 const x = (ww - drawWidth) / 2;
 const y = (wh - drawHeight) / 2;
 ctx.drawImage(img, x, y, drawWidth, drawHeight);

 var data = ctx.getImageData(0, 0, ww, wh).data;
 ctx.clearRect(0, 0, canvas.width, canvas.height);
 ctx.globalCompositeOperation = "screen";
 particles = [];
 for(var i=0;i<ww;i+=Math.round(ww/150)){
 for(var j=0;j<wh;j+=Math.round(ww/150)){
 if(data[ ((i + j*ww)*4) + 3] > 150){
 particles.push(new Particle(i,j));
 }
 }
 }
 amount = particles.length;
}

function onMouseClick(){
 radius++;
 if(radius ===5){
 radius = 0;
 }
}

function render(a) {
 requestAnimationFrame(render);
 ctx.clearRect(0, 0, canvas.width, canvas.height);
 for (var i = 0; i < amount; i++) {
 particles[i].render();
 }
};

// 监听图片加载完成后初始化
const img = document.querySelector("#sourceImg");
img.addEventListener("load", function() {
 initScene();
 requestAnimationFrame(render);
});

// 原有事件监听保留
window.addEventListener("resize", initScene);
window.addEventListener("mousemove", onMouseMove);
window.addEventListener("touchmove", onTouchMove);
window.addEventListener("click", onMouseClick);
window.addEventListener("touchend", onTouchEnd);
</script>
</body>
</html>
几个注意点
  • 跨域问题:如果你的图片是来自其他域名的,需要确保服务器设置了CORS允许跨域,否则getImageData会报错。最简单的办法是用本地图片或者同域的图片。
  • 图片缩放:代码里做了自适应Canvas的缩放处理,你可以根据需求调整缩放方式,比如铺满Canvas或者保持原大小。
  • 粒子密度:代码里的Math.round(ww/150)控制粒子的密度,数值越小粒子越密,反之越稀疏,你可以根据效果调整。

内容的提问来源于stack exchange,提问作者Kenneth Abrahamsson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:49:12