完整项目预览----预览地址;javascript
在canvas中,能够经过getImageData()
方法来获取像素数据。html
ctx.fillStyle = '#ff0000'; ctx.fillRect(0, 0, 1, 1); const imageData = ctx.getImageData(0, 0, 1, 1);
imageData
有三个属性:java
data
:数组,包含了像素信息,每一个像素会有四个长度,如[255,0,0,255, ... ,255,127,0,255]
,分别表明该像素的RGBA值。width
:imageData
对象的宽。height
:imageData
对象的高。首先在canvas
上写上某种颜色文字,再去分析像素数据(好比改像素是否有透明度等),而后本身记录下该像素点的位置。git
下例是经过改变像素的数据而从新写出来的文字。程序员
ctx.font = 'bold 40px Arial'; ctx.textBaseline = 'middle'; ctx.textAlign = 'center'; ctx.fillText('你好啊', 60, 20); document.querySelector('#button').addEventListener('click', function(){ const imgData = ctx.getImageData(0, 0, 120, 40); for(let i = 0;i < imgData.data.length; i+=4){ if(imgData.data[i + 3] == 0) continue; imgData.data[i] = 255; imgData.data[i + 1] = 0; imgData.data[i + 2] = 0; // imgData.data[i + 3] = 255; 这个表明的是透明度 透明度不变 255最高 0最低 } ctx.putImageData(imgData,120,0); });
这段代码只是示例说明一下,实际上才不会有人这么脑残去换颜色吧。github
要获取点的位置,首先要将字写在画布上,可是字又不能让别人看到。因此能够动态建立一个画布,这个画布不会append
到任何节点上,只会用于写字。canvas
const cache = document.createElement('canvas');
segmentfault
将宽高等与展现的画布设置成同样的。(不贴这部分的代码了)数组
建立一个对象,用于获取点的位置app
const ShapeBuilder = { //初始化字的对齐方式等,我认为middle 与 center比较好计算一点 init(width, height){ this.width = width; this.height = height; this.ctx = cache.getContext('2d'); this.ctx.textBaseline = 'middle'; this.ctx.textAlign = 'center'; }, //获取位置以前必须先要写入文字。 这里的size=40是默认值 write(words, x, y, size = 40){ //清除以前写的字。 this.ctx.clearRect(0, 0, this.width, this.height); this.font = `bold ${size}px Arial`; this.ctx.fillText(words, x, y); //记录当前文字的位置,方便计算获取像素的区域 this.x = x; this.y = y; this.size = size; this.length = words.length; }, getPositions(){ //由于imgData数据很是的大,因此尽量的缩小获取数据的范围。 const xStart = this.x - (this.length / 2) * this.size, xEnd = this.x + (this.length / 2) * this.size, yStart = this.y - this.size / 2, yEnd = this.y + this.size / 2, //getImageData(起点x, 起点y, 宽度, 高度); data = this.ctx.getImageData(xStart, yStart, this.size * this.length, this.size).data; //间隔 (下面有介绍) const gap = 4; let positions = [], x = xStart, y = yStart; for(var i = 0;i < data.length; i += 4 * gap){ if(data[i+3] > 0){ positions.push({x, y}); } x += gap; if(x >= xEnd){ x = xStart; y += gap; i += (gap - 1) * 4 * (xEnd - xStart); } } return positions; } } ShapeBuilder.init();
关于gap
:在循环imgData
数组的时候,数据量太大可能会形成卡顿,因此可使用间隔来获取坐标点的方法。不过可能会形成文字部分地方缺失。就须要我的来权衡利弊,本身来调整了。
gap
的值必须能被xEnd-xStart
给整除,否则会形成获取坐标点错位的后果。
关于canvas
中middle
与center
的规则:
this.ctx.font = 'bold 40px Arial'; this.ctx.fillText('你好',40 ,20);
效果以下图所示
fillText
设置的坐标点恰好会是整个字的中点,就是图中middle
与center
的交点。其实以其它对齐方式也是能够的,看我的喜爱。
更多的对齐规则参考HTML 5 Canvas 参考手册的文本。
微粒应该随机生成,而后移动到指定的位置去。
微粒类的属性:
自身当前位置(x,y), 目标位置:(xEnd,yEnd),自身大小(size),自身颜色(color),移动快慢(e)
方法:go()
:每一帧都要移动一段距离,render()
:渲染出微粒(我用心形的形状)
class Particle { constructor({x, y, size = 2, color, xEnd, yEnd, e = 60} = {}){ this.x = x; this.y = y; this.size = size; this.color = color || `hsla(${Math.random() * 360}, 90%, 65%, 1)`; this.xEnd = xEnd; this.yEnd = yEnd; //通过e帧以后到达目标地点 this.e = e; //计算每一帧走过的距离 this.dx = (xEnd - x) / e; this.dy = (yEnd - y) / e; } go(){ //到目的后保持不动 (其实这里也能够搞点事情的) if(--this.e <= 0) { this.x = this.xEnd; this.y = this.yEnd; return ; } this.x += this.dx; this.y += this.dy; } render(ctx){ this.go(); //下面是画出心型的贝塞尔曲线 ctx.beginPath(); ctx.fillStyle = this.color; ctx.moveTo(this.x + 0.5 * this.size, this.y + 0.3 * this.size); ctx.bezierCurveTo(this.x + 0.1 * this.size, this.y, this.x, this.y + 0.6 * this.size, this.x + 0.5 * this.size, this.y + 0.9 * this.size); ctx.bezierCurveTo(this.x + 1 * this.size, this.y + 0.6 * this.size, this.x + 0.9 * this.size, this.y, this.x + 0.5 * this.size, this.y + 0.3 * this.size); ctx.closePath(); ctx.fill(); return true; } }
微粒类最基本的属性与方法就是这些,若是要让粒子更好看一点,或者更生动一点,能够本身添加一些属性与方法。
const canvas = { init(){ //设置一些属性 this.setProperty(); //建立微粒 this.createParticles(); //canvas的循环 this.loop(); }, setProperty(){ this.ctx = studio.getContext('2d'); this.width = document.body.clientWidth; this.height = document.body.clientHeight; this.particles = []; }, createParticles(){ let dots; //ShapeBuilder.write(words, x, y, size) ShapeBuilder.write('每一个字都是',this.width / 2, this.height / 3, 120); dots = ShapeBuilder.getPositions(6); ShapeBuilder.write('爱你的模样', this.width / 2, this.height * 2 / 3, 120); dots = dots.concat(ShapeBuilder.getPositions(6)); //dots已经获取到了字的坐标点 //每个微粒的目标地点都是dots的坐标 //每个微粒都随机出生在画布的某个位置 for(let i = 0; i < dots.length; i++){ this.particles.push(new Particle({ xEnd:dots[i].x, yEnd:dots[i].y , x: Math.random() * this.width, y: Math.random() * this.height, size:6, color:'hsla(360, 90%, 65%, 1)' })); } }, loop(){ //每一帧清除画布,而后再渲染微粒就能够了 requestAnimationFrame(this.loop.bind(this)); this.ctx.clearRect(0, 0, this.width, this.height); for(var i = 0; i < this.particles.length; i++){ this.particles[i].render(this.ctx); } } } canvas.init();
若是想要给每一个粒子加上小尾巴的话,那么在每一帧的时候,就不要清除画布,并且覆盖一层有透明度的底色。
//修改loop方法 //this.ctx.clearRect(0, 0, this.width, this.height); this.ctx.fillStyle = 'rgba(0,0,0,0.2)'; this.ctx.fillRect(0, 0, this.width, this.height);
这样的话会变成以下效果
在这这篇文章的时候,并无注意太多细节,好比gap
应该是能够被设置的,或者是一个被特殊标注的常量,而不该该随便写在方法中。对于本例的代码,切勿生搬硬套,重要的是要理解原理,以及本身亲自动手尝试。
我也是在写这篇文章的过程当中,才发现了以前获取position
一个不精准的地方。
这里只讲了粒子效果最基础的用法,实际上还能够作出不少很是炫酷的效果
好比在粒子到达目的地后还能够抖动什么的
粒子形状、颜色的变化等等。
这个项目还能够搞不少事情的,你们也能够本身多来尝试弄些更加炫酷的效果。
烟花效果能够看一下个人上一篇,程序员的小浪漫----烟火
若是以为还不错,请star一个吧。
github上的一个项目---- shape-shifter
这个项目我以为很是不错,惋惜做者都消失好多年了。
codepen.io 上的一个做品 ---- Love In Hearts