求助:如何将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
相关产品推荐
相关产品推荐

