完整項目預覽----預覽地址;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