ホームページ > 記事 > ウェブフロントエンド > キャンバスパーティクルシステムの構築方法を詳しく解説
次のエディタでは、キャンバス パーティクル システムの構築について詳しく説明します。編集者はこれがとても良いと思ったので、参考として共有します。エディターをフォローして見てみましょう
前に述べたこと
この記事では、imageData オブジェクトの最も基本的な理論的知識から始めて、キャンバス パーティクル システムの構築について詳しく紹介します
imageData
画像データについて imageDataにはgetImageData()、putImageData()、createImageData()の3つのメソッドがあります
【getImageData()】
2DコンテキストはgetImageData()を通じて元の画像データを取得できます。このメソッドは、画面領域の x 座標と y 座標、領域のピクセル幅と高さの 4 つのパラメータを受け取ります。たとえば、左上隅の座標が (10,5) である領域の画像データを取得します。サイズは 50*50 ピクセル、次のコードを使用できます:
var imageData = context.getImageData(10,5,50,50);
1、width: 対角線の幅を表します。 imageData
2のheight:imageDataオブジェクトを表します
3.dataのheightは画像内の各ピクセルのデータを格納する配列です。データ配列では、各ピクセルに赤、緑、青、透明度を表す 4 つの要素が格納されます。 [注] 画像内にピクセルが何個あるか、データの長さはピクセル数を掛けたものと等しくなります。 4
//第一个像素如下 var data = imageData.data; var red = data[0]; var green = data[1]; var blue = data[2]; var alpha = data[3];配列の各要素の値は0〜255の範囲であり、元の画像データに直接アクセスできれば、さまざまな方法でデータを操作できます
【注意】getImageDataを使用したい場合。 () キャンバスを取得します。drawImage() メソッドが含まれている場合、このメソッドの URL はドメインを越えることはできません。
[createImageData()]
createImageData(width,height) メソッドは、新しい空の ImageData オブジェクトを作成します。新しいオブジェクトのデフォルトのピクセル値は透明な黒で、rgba(0,0,0,0)に相当します
var imgData = context.createImageData(100,100);【putImageData()】
putImageData() メソッドは画像データを元のオブジェクトに戻します。指定された ImageData オブジェクトからキャンバスを取得する場合、このメソッドには次のパラメーターがあります
imgData:要放回画布的ImageData对象(必须) x:imageData对象的左上角的x坐标(必须) y:imageData对象的左上角的y坐标(必须) dirtyX:在画布上放置图像的水平位置(可选) dirtyY:在画布上放置图像的垂直位置(可选) dirtyWidth:在画布上绘制图像所使用的宽度(可选) dirtyHeight:在画布上绘制图像所使用的高度(可选)[注] パラメーター 3 ~ 7 は存在しないか、すべて存在します
context.putImageData(imgData,0,0); imgData,0,0, 50,50,200,200);
パーティクルの書き込み
パーティクルとは、画像データimageData内の各ピクセルを指します。以下は完全書き込みとパーティクル書き込みを説明するための簡単な例です【完全書き込み】
200*200のcanvas1に文字「リトルマッチ」が存在し、canvas1全体を同じサイズのcanvas2に画像データとして書き込みます<canvas id="drawing1" style="border:1px solid black"></canvas> <canvas id="drawing2" style="border:1px solid black"></canvas> <script> var drawing1 = document.getElementById('drawing1'); var drawing2 = document.getElementById('drawing2'); if(drawing1.getContext){ var cxt = drawing1.getContext('2d'); var cxt2 = drawing2.getContext('2d'); var W = drawing1.width = drawing2.width = 200; var H = drawing1.height = drawing2.height = 200; var str = '小火柴'; cxt.textBaseline = 'top'; var sh = 60; cxt.font = sh + 'px 宋体' var sw = cxt.measureText(str).width; if(sw > W){ sw = W; } cxt.fillText(str,(W - sw)/2,(H - sh)/2,W); //获取imageData var imageData = cxt.getImageData(0,0,W,H); //写入drawing2中 cxt2.putImageData(imageData,0,0); </script>
【パーティクルの書き込み】
完全な書き込みの場合、各ピクセルを細かく制御したい場合は、パーティクルの書き込みを使用する必要があります。 Canvas1 には空白領域が多く、「little match」と書かれた領域のみが有効です。したがって、画像データ imageData の透明度に基づいてパーティクルをフィルタリングすることができ、透明度が 0 より大きいパーティクルのみがフィルタリングされて除外されます
<canvas id="drawing1" style="border:1px solid black"></canvas> <canvas id="drawing2" style="border:1px solid black"></canvas> <script> var drawing1 = document.getElementById('drawing1'); var drawing2 = document.getElementById('drawing2'); if(drawing1.getContext){ var cxt = drawing1.getContext('2d'); var cxt2 = drawing2.getContext('2d'); var W = drawing1.width = drawing2.width = 200; var H = drawing1.height = drawing2.height = 200; var str = '小火柴'; cxt.textBaseline = 'top'; var sh = 60; cxt.font = sh + 'px 宋体' var sw = cxt.measureText(str).width; if(sw > W){ sw = W; } cxt.fillText(str,(W - sw)/2,(H - sh)/2,W); //获取imageData var imageData = cxt.getImageData(0,0,W,H); //写入drawing2中 cxt2.putImageData(setData(imageData),0,0); function setData(imageData){ //从imageData对象中取得粒子,并存储到dots数组中 var dots = []; for(var i = 0; i < W; i++){ for(var j = 0; j < H ;j++){ //data值中的红色值 var k = 4*(i + j*W); //data值中的透明度 if(imageData.data[k+3] > 0){ //将透明度大于0的data中的红色值保存到dots数组中 dots.push(k); } } } //40000 2336 console.log(i*j,dots.length); //新建一个imageData,并将筛选后的粒子信息保存到新建的imageData中 var oNewImage = cxt.createImageData(W,H); for(var i = 0; i < dots.length; i++){ oNewImage.data[dots[i]+0] = imageData.data[dots[i]+0]; oNewImage.data[dots[i]+1] = imageData.data[dots[i]+1]; oNewImage.data[dots[i]+2] = imageData.data[dots[i]+2]; oNewImage.data[dots[i]+3] = imageData.data[dots[i]+3]; } return oNewImage; } } </script>
結果は同じように見えますが、canvas2 は 40000 個のパーティクルのうち 2336 個のみを使用しますCanvas1 内
パーティクルが完全に書き込まれると、キャンバスのコピー アンド ペーストと同じ効果が得られます。パーティクルをフィルタリングすると、素晴らしい効果が現れます 【ふるい分け】
パーティクルを取得するときに、幅の値 * 高さの値の二重ループが使用され、両方とも 1 を加算することで増加します。 1を追加する代わりにnを追加すると、逐次フィルタリングの効果を得ることができます<canvas id="drawing1" style="border:1px solid black"></canvas> <canvas id="drawing2" style="border:1px solid black"></canvas> <p id="con"> <button>1</button> <button>2</button> <button>3</button> <button>4</button> <button>5</button> </p> <script> var oCon = document.getElementById('con'); oCon.onclick = function(e){ e = e || event; var tempN = e.target.innerHTML; if(tempN){ cxt2.clearRect(0,0,W,H); cxt2.putImageData(setData(imageData,Number(tempN)),0,0); } } var drawing1 = document.getElementById('drawing1'); var drawing2 = document.getElementById('drawing2'); if(drawing1.getContext){ var cxt = drawing1.getContext('2d'); var cxt2 = drawing2.getContext('2d'); var W = drawing1.width = drawing2.width = 200; var H = drawing1.height = drawing2.height = 200; var str = '小火柴'; cxt.textBaseline = 'top'; var sh = 60; cxt.font = sh + 'px 宋体' var sw = cxt.measureText(str).width; if(sw > W){ sw = W; } cxt.fillText(str,(W - sw)/2,(H - sh)/2,W); //获取imageData var imageData = cxt.getImageData(0,0,W,H); //写入drawing2中 cxt2.putImageData(setData(imageData,1),0,0); function setData(imageData,n){ //从imageData对象中取得粒子,并存储到dots数组中 var dots = []; for(var i = 0; i < W; i+=n){ for(var j = 0; j < H ;j+=n){ //data值中的红色值 var k = 4*(i + j*W); //data值中的透明度 if(imageData.data[k+3] > 0){ //将透明度大于0的data中的红色值保存到dots数组中 dots.push(k); } } } //新建一个imageData,并将筛选后的粒子信息保存到新建的imageData中 var oNewImage = cxt.createImageData(W,H); for(var i = 0; i < dots.length; i++){ oNewImage.data[dots[i]+0] = imageData.data[dots[i]+0]; oNewImage.data[dots[i]+1] = imageData.data[dots[i]+1]; oNewImage.data[dots[i]+2] = imageData.data[dots[i]+2]; oNewImage.data[dots[i]+3] = imageData.data[dots[i]+3]; } return oNewImage; } } </script>
【ランダムフィルタリング】
逐次フィルタリングの使用に加えて、ランダムフィルタリングも使用できます。 二重ループで得られた粒子の位置情報をドット配列に配置します。 splice() メソッドでフィルターし、フィルターされた位置情報を新しく作成した newDots 配列に入れ、createImageData() を使用して新しい画像データ オブジェクトを作成し、
<canvas id="drawing1" style="border:1px solid black"></canvas> <canvas id="drawing2" style="border:1px solid black"></canvas> <p id="con"> <button>1000</button> <button>2000</button> <button>3000</button> <button>4000</button> </p> <script> var oCon = document.getElementById('con'); oCon.onclick = function(e){ e = e || event; var tempN = e.target.innerHTML; if(tempN){ cxt2.clearRect(0,0,W,H); cxt2.putImageData(setData(imageData,1,Number(tempN)),0,0); } } var drawing1 = document.getElementById('drawing1'); var drawing2 = document.getElementById('drawing2'); if(drawing1.getContext){ var cxt = drawing1.getContext('2d'); var cxt2 = drawing2.getContext('2d'); var W = drawing1.width = drawing2.width = 200; var H = drawing1.height = drawing2.height = 200; var str = '小火柴'; cxt.textBaseline = 'top'; var sh = 60; cxt.font = sh + 'px 宋体' var sw = cxt.measureText(str).width; if(sw > W){ sw = W; } cxt.fillText(str,(W - sw)/2,(H - sh)/2,W); //获取imageData var imageData = cxt.getImageData(0,0,W,H); //写入drawing2中 cxt2.putImageData(setData(imageData,1),0,0); function setData(imageData,n,m){ //从imageData对象中取得粒子,并存储到dots数组中 var dots = []; for(var i = 0; i < W; i+=n){ for(var j = 0; j < H ;j+=n){ //data值中的红色值 var k = 4*(i + j*W); //data值中的透明度 if(imageData.data[k+3] > 0){ //将透明度大于0的data中的红色值保存到dots数组中 dots.push(k); } } } //筛选粒子,仅保存m个到newDots数组中。如果不传入m,则不进行筛选 var newDots = []; if(m && (dots.length > m)){ for(var i = 0; i < m; i++){ newDots.push(Number(dots.splice(Math.floor(Math.random()*dots.length),1))); } }else{ newDots = dots; } //新建一个imageData,并将筛选后的粒子信息保存到新建的imageData中 var oNewImage = cxt.createImageData(W,H); for(var i = 0; i < newDots.length; i++){ oNewImage.data[newDots[i]+0] = imageData.data[newDots[i]+0]; oNewImage.data[newDots[i]+1] = imageData.data[newDots[i]+1]; oNewImage.data[newDots[i]+2] = imageData.data[newDots[i]+2]; oNewImage.data[newDots[i]+3] = imageData.data[newDots[i]+3]; } return oNewImage; } } </script>
ピクセル表示
を返します。粒子フィルタリングを使用して、ピクセル上にテキストを表示する効果を実現します。ピクセル表示とは、不鮮明なエフェクトから完全な表示に徐々に移行することを意味します[順次ピクセル表示] 順次ピクセル表示の実装原理は非常にシンプルです。たとえば、合計 2000 個のパーティクルと合計 10 レベルの移行エフェクトがあります。 。次に、10 個のアレイを使用して、それぞれ 200、400、600、800、100、1200、1400、1600、1800、2000 個のパーティクルを保存します。次に、タイマーを使用して徐々に表示します
<canvas id="drawing1" style="border:1px solid black"></canvas> <button id="btn">开始显字</button> <script> var drawing1 = document.getElementById('drawing1'); if(drawing1.getContext){ var cxt = drawing1.getContext('2d'); var W = drawing1.width = 200; var H = drawing1.height = 200; var str = '小火柴'; cxt.textBaseline = 'top'; var sh = 60; cxt.font = sh + 'px 宋体' var sw = cxt.measureText(str).width; if(sw > W){ sw = W; } cxt.fillText(str,(W - sw)/2,(H - sh)/2,W); //获取imageData var imageData = cxt.getImageData(0,0,W,H); cxt.clearRect(0,0,W,H); //获得10组粒子 var imageDataArr = []; var n = 10; var index = 0; for(var i = n; i > 0; i--){ imageDataArr.push(setData(imageData,i)); } var oTimer = null; btn.onclick = function(){ clearTimeout(oTimer); showData(); } function showData(){ oTimer = setTimeout(function(){ cxt.clearRect(0,0,W,H); //写入drawing1中 cxt.putImageData(imageDataArr[index++],0,0); //迭代函数 showData(); if(index == 10){ index = 0; clearTimeout(oTimer); } },100); } function setData(imageData,n,m){ //从imageData对象中取得粒子,并存储到dots数组中 var dots = []; for(var i = 0; i < W; i+=n){ for(var j = 0; j < H ;j+=n){ //data值中的红色值 var k = 4*(i + j*W); //data值中的透明度 if(imageData.data[k+3] > 0){ //将透明度大于0的data中的红色值保存到dots数组中 dots.push(k); } } } //筛选粒子,仅保存m个到newDots数组中。如果不传入m,则不进行筛选 var newDots = []; if(m && (dots.length > m)){ for(var i = 0; i < m; i++){ newDots.push(Number(dots.splice(Math.floor(Math.random()*dots.length),1))); } }else{ newDots = dots; } //新建一个imageData,并将筛选后的粒子信息保存到新建的imageData中 var oNewImage = cxt.createImageData(W,H); for(var i = 0; i < newDots.length; i++){ oNewImage.data[newDots[i]+0] = imageData.data[newDots[i]+0]; oNewImage.data[newDots[i]+1] = imageData.data[newDots[i]+1]; oNewImage.data[newDots[i]+2] = imageData.data[newDots[i]+2]; oNewImage.data[newDots[i]+3] = imageData.data[newDots[i]+3]; } return oNewImage; } } </script>【ランダムピクセル表示】ランダムピクセル表示の原理は似ており、異なる番号を持つランダムピクセルの配列を複数保存するだけです
<canvas id="drawing1" style="border:1px solid black"></canvas> <button id="btn">开始显字</button> <script> var drawing1 = document.getElementById('drawing1'); if(drawing1.getContext){ var cxt = drawing1.getContext('2d'); var W = drawing1.width = 200; var H = drawing1.height = 200; var str = '小火柴'; cxt.textBaseline = 'top'; var sh = 60; cxt.font = sh + 'px 宋体' var sw = cxt.measureText(str).width; if(sw > W){ sw = W; } cxt.fillText(str,(W - sw)/2,(H - sh)/2,W); //获取imageData var imageData = cxt.getImageData(0,0,W,H); cxt.clearRect(0,0,W,H); //获得10组粒子 var imageDataArr = []; var n = 10; var index = 0; for(var i = n; i > 0; i--){ imageDataArr.push(setData(imageData,1,i)); } var oTimer = null; btn.onclick = function(){ clearTimeout(oTimer); showData(); } function showData(){ oTimer = setTimeout(function(){ cxt.clearRect(0,0,W,H); //写入drawing1中 cxt.putImageData(imageDataArr[index++],0,0); //迭代函数 showData(); if(index == 10){ clearTimeout(oTimer); index = 0; } },100); } function setData(imageData,n,m){ //从imageData对象中取得粒子,并存储到dots数组中 var dots = []; for(var i = 0; i < W; i+=n){ for(var j = 0; j < H ;j+=n){ //data值中的红色值 var k = 4*(i + j*W); //data值中的透明度 if(imageData.data[k+3] > 0){ //将透明度大于0的data中的红色值保存到dots数组中 dots.push(k); } } } //筛选粒子,仅保存dots.length/m个到newDots数组中 var newDots = []; var len = Math.floor(dots.length/m); for(var i = 0; i < len; i++){ newDots.push(Number(dots.splice(Math.floor(Math.random()*dots.length),1))); } //新建一个imageData,并将筛选后的粒子信息保存到新建的imageData中 var oNewImage = cxt.createImageData(W,H); for(var i = 0; i < newDots.length; i++){ oNewImage.data[newDots[i]+0] = imageData.data[newDots[i]+0]; oNewImage.data[newDots[i]+1] = imageData.data[newDots[i]+1]; oNewImage.data[newDots[i]+2] = imageData.data[newDots[i]+2]; oNewImage.data[newDots[i]+3] = imageData.data[newDots[i]+3]; } return oNewImage; } } </script>パーティクルアニメーション
粒子动画并不是粒子在做动画,而是通过getImageData()方法获得粒子的随机坐标和最终坐标后,通过fillRect()方法绘制的小方块在做运动。使用定时器,不断的绘制坐标变化的小方块,以此来产生运动的效果
【随机位置】
<canvas id="drawing1" style="border:1px solid black"></canvas> <button id="btn1">开始显字</button> <button id="btn2">重新混乱</button> <script> var drawing1 = document.getElementById('drawing1'); if(drawing1.getContext){ var cxt = drawing1.getContext('2d'); var W = drawing1.width = 200; var H = drawing1.height = 200; var str = '小火柴'; cxt.textBaseline = 'top'; var sh = 60; cxt.font = sh + 'px 宋体' var sw = cxt.measureText(str).width; if(sw > W){ sw = W; } cxt.fillText(str,(W - sw)/2,(H - sh)/2,W); //获取imageData var imageData = cxt.getImageData(0,0,W,H); cxt.clearRect(0,0,W,H); function setData(imageData,n,m){ //从imageData对象中取得粒子,并存储到dots数组中 var dots = []; //dots的索引 var index = 0; for(var i = 0; i < W; i+=n){ for(var j = 0; j < H ;j+=n){ //data值中的红色值 var k = 4*(i + j*W); //data值中的透明度 if(imageData.data[k+3] > 0){ //将透明度大于0的data中的红色值保存到dots数组中 dots.push(k); dots[index++] = { 'index':index, 'x':i, 'y':j, 'red':k, 'randomX':Math.random()*W, 'randomY':Math.random()*H, } } } } //筛选粒子,仅保存dots.length/m个到newDots数组中 var newDots = []; var len = Math.floor(dots.length/m); for(var i = 0; i < len; i++){ newDots.push(dots.splice(Math.floor(Math.random()*dots.length),1)[0]); } return newDots; } //获得粒子数组 var dataArr = setData(imageData,1,1); var oTimer1 = null; var oTimer2 = null; btn1.onclick = function(){ clearTimeout(oTimer1); showData(10); } btn2.onclick = function(){ clearTimeout(oTimer2); showRandom(10); } function showData(n){ oTimer1 = setTimeout(function(){ cxt.clearRect(0,0,W,H); for(var i = 0; i < dataArr.length; i++){ var temp = dataArr[i]; var x0 = temp.randomX; var y0 = temp.randomY; var disX = temp.x - temp.randomX; var disY = temp.y - temp.randomY; cxt.fillRect(x0 + disX/n,y0 + disY/n,1,1); } showData(n-1); if(n === 1){ clearTimeout(oTimer1); } },60); } function showRandom(n){ oTimer2 = setTimeout(function fn(){ cxt.clearRect(0,0,W,H); for(var i = 0; i < dataArr.length; i++){ var temp = dataArr[i]; var x0 = temp.x; var y0 = temp.y; var disX = temp.randomX - temp.x; var disY = temp.randomY - temp.y; cxt.fillRect(x0 + disX/n,y0 + disY/n,1,1); } showRandom(n-1); if(n === 1){ clearTimeout(oTimer2); } },60); } } </script>
【飘入效果】
飘入效果与随机显字的原理相似,不再赘述
<canvas id="drawing1" style="border:1px solid black"></canvas> <button id="btn1">左上角飘入</button> <script> var drawing1 = document.getElementById('drawing1'); if(drawing1.getContext){ var cxt = drawing1.getContext('2d'); var W = drawing1.width = 200; var H = drawing1.height = 200; var str = '小火柴'; cxt.textBaseline = 'top'; var sh = 60; cxt.font = sh + 'px 宋体' var sw = cxt.measureText(str).width; if(sw > W){ sw = W; } cxt.fillText(str,(W - sw)/2,(H - sh)/2,W); //获取imageData var imageData = cxt.getImageData(0,0,W,H); cxt.clearRect(0,0,W,H); function setData(imageData,n,m){ //从imageData对象中取得粒子,并存储到dots数组中 var dots = []; //dots的索引 var index = 0; for(var i = 0; i < W; i+=n){ for(var j = 0; j < H ;j+=n){ //data值中的红色值 var k = 4*(i + j*W); //data值中的透明度 if(imageData.data[k+3] > 0){ //将透明度大于0的data中的红色值保存到dots数组中 dots.push(k); dots[index++] = { 'index':index, 'x':i, 'y':j, 'red':k, 'randomX':Math.random()*W, 'randomY':Math.random()*H, } } } } //筛选粒子,仅保存dots.length/m个到newDots数组中 var newDots = []; var len = Math.floor(dots.length/m); for(var i = 0; i < len; i++){ newDots.push(dots.splice(Math.floor(Math.random()*dots.length),1)[0]); } return newDots; } //获得粒子数组 var dataArr = setData(imageData,1,1); var oTimer1 = null; btn1.onclick = function(){ clearTimeout(oTimer1); showData(10); } function showData(n){ oTimer1 = setTimeout(function(){ cxt.clearRect(0,0,W,H); for(var i = 0; i < dataArr.length; i++){ var temp = dataArr[i]; var x0 = 0; var y0 = 0; var disX = temp.x - 0; var disY = temp.y - 0; cxt.fillRect(x0 + disX/n,y0 + disY/n,1,1); } showData(n-1); if(n === 1){ clearTimeout(oTimer1); } },60); } } </script>
鼠标交互
一般地,粒子的鼠标交互都与isPointInPath(x,y)方法有关
【移入变色】
当鼠标接近粒子时,该粒子变红。实现原理很简单。鼠标移动时,通过isPointInPath(x,y)方法检测,有哪些粒子处于当前指针范围内。如果处于,绘制1像素的红色矩形即可
<canvas id="drawing1" style="border:1px solid black"></canvas> <script> var drawing1 = document.getElementById('drawing1'); if(drawing1.getContext){ var cxt = drawing1.getContext('2d'); var W = drawing1.width = 200; var H = drawing1.height = 200; var str = '小火柴'; cxt.textBaseline = 'top'; var sh = 60; cxt.font = sh + 'px 宋体' var sw = cxt.measureText(str).width; if(sw > W){ sw = W; } cxt.fillText(str,(W - sw)/2,(H - sh)/2,W); //获取imageData var imageData = cxt.getImageData(0,0,W,H); function setData(imageData,n,m){ //从imageData对象中取得粒子,并存储到dots数组中 var dots = []; //dots的索引 var index = 0; for(var i = 0; i < W; i+=n){ for(var j = 0; j < H ;j+=n){ //data值中的红色值 var k = 4*(i + j*W); //data值中的透明度 if(imageData.data[k+3] > 0){ //将透明度大于0的data中的红色值保存到dots数组中 dots.push(k); dots[index++] = { 'index':index, 'x':i, 'y':j, 'red':k, 'randomX':Math.random()*W, 'randomY':Math.random()*H, } } } } //筛选粒子,仅保存dots.length/m个到newDots数组中 var newDots = []; var len = Math.floor(dots.length/m); for(var i = 0; i < len; i++){ newDots.push(dots.splice(Math.floor(Math.random()*dots.length),1)[0]); } return newDots; } //获得粒子数组 var dataArr = setData(imageData,1,1); //鼠标移动时,当粒子距离鼠标指针小于10时,则进行相关操作 drawing1.onmousemove = function(e){ e = e || event; var x = e.clientX - drawing1.getBoundingClientRect().left; var y = e.clientY - drawing1.getBoundingClientRect().top; cxt.beginPath(); cxt.arc(x,y,10,0,Math.PI*2); for(var i = 0; i < dataArr.length; i++){ var temp = dataArr[i]; if(cxt.isPointInPath(temp.x,temp.y)){ cxt.fillStyle = 'red'; cxt.fillRect(temp.x,temp.y,1,1); } } } } </script>
【远离鼠标】
鼠标点击时,以鼠标指针为圆心的一定范围内的粒子需要移动到该范围以外。一段时间后,粒子回到原始位置
实现原理并不复杂,使用isPointInPath(x,y)方法即可,如果粒子处于当前路径中,则沿着鼠标指针与粒子坐标组成的直线方向,移动到路径的边缘
<canvas id="drawing1" style="border:1px solid black"></canvas> <script> var drawing1 = document.getElementById('drawing1'); if(drawing1.getContext){ var cxt = drawing1.getContext('2d'); var W = drawing1.width = 200; var H = drawing1.height = 200; var str = '小火柴'; cxt.textBaseline = 'top'; var sh = 60; cxt.font = sh + 'px 宋体' var sw = cxt.measureText(str).width; if(sw > W){ sw = W; } //渲染文字 cxt.fillText(str,(W - sw)/2,(H - sh)/2,W); //获取imageData var imageData = cxt.getImageData(0,0,W,H); cxt.clearRect(0,0,W,H); function setData(imageData,n,m){ //从imageData对象中取得粒子,并存储到dots数组中 var dots = []; //dots的索引 var index = 0; for(var i = 0; i < W; i+=n){ for(var j = 0; j < H ;j+=n){ //data值中的红色值 var k = 4*(i + j*W); //data值中的透明度 if(imageData.data[k+3] > 0){ //将透明度大于0的data中的红色值保存到dots数组中 dots.push(k); dots[index++] = { 'index':index, 'x':i, 'y':j, 'red':k, 'randomX':Math.random()*W, 'randomY':Math.random()*H, 'mark':false } } } } //筛选粒子,仅保存dots.length/m个到newDots数组中 var newDots = []; var len = Math.floor(dots.length/m); for(var i = 0; i < len; i++){ newDots.push(dots.splice(Math.floor(Math.random()*dots.length),1)[0]); } return newDots; } //获得粒子数组 var dataArr = setData(imageData,2,1); //将筛选后的粒子信息保存到新建的imageData中 var oNewImage = cxt.createImageData(W,H); for(var i = 0; i < dataArr.length; i++){ for(var j = 0; j < 4; j++){ oNewImage.data[dataArr[i].red+j] = imageData.data[dataArr[i].red+j]; } } //写入canvas中 cxt.putImageData(oNewImage,0,0); //设置鼠标检测半径为r var r = 20; //鼠标移动时,当粒子距离鼠标指针小于20时,则进行相关操作 drawing1.onmousedown = function(e){ e = e || event; var x = e.clientX - drawing1.getBoundingClientRect().left; var y = e.clientY - drawing1.getBoundingClientRect().top; cxt.beginPath(); cxt.arc(x,y,r,0,Math.PI*2); for(var i = 0; i < dataArr.length; i++){ var temp = dataArr[i]; if(cxt.isPointInPath(temp.x,temp.y)){ temp.mark = true; var angle = Math.atan2((temp.y - y),(temp.x - x)); temp.endX = x - r*Math.cos(angle); temp.endY = y - r*Math.sin(angle); var disX = temp.x - temp.endX; var disY = temp.y - temp.endY; cxt.fillStyle = '#fff'; cxt.fillRect(temp.x,temp.y,1,1); cxt.fillStyle = '#000'; cxt.fillRect(temp.endX,temp.endY,1,1); dataRecovery(10); }else{ temp.mark = false; } } var oTimer = null; function dataRecovery(n){ clearTimeout(oTimer); oTimer = setTimeout(function(){ cxt.clearRect(0,0,W,H); for(var i = 0; i < dataArr.length; i++){ var temp = dataArr[i]; if(temp.mark){ var x0 = temp.endX; var y0 = temp.endY; var disX = temp.x - x0; var disY = temp.y - y0; cxt.fillRect(x0 + disX/n,y0 + disY/n,1,1); }else{ cxt.fillRect(temp.x,temp.y,1,1); } } dataRecovery(n-1); if(n === 1){ clearTimeout(oTimer); } },17); } } } </script>
综合实例
下面将上面的效果制作为一个可编辑的综合实例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <canvas id="drawing1" style="border:1px solid black"></canvas> <br> <p style="margin-bottom:10px"> <span>粒子设置:</span> <input type="text" id="textValue" value="小火柴的蓝色理想"> <button id="btnSetText">文字设置确认</button> <button id="btnchoose2">按序筛选</button> <button id="btnchoose3">随机筛选</button> <button id="btnchoose1">不筛选</button> </p> <p style="margin-bottom:10px"> <span>粒子效果:</span> <button id="btn1">按序显字</button> <button id="btn2">随机显字</button> <button id="btn3">混乱聚合</button> <button id="btn4">重新混乱</button> </p> <p> <span>鼠标效果:</span> <span>1、鼠标移到文字上时,文字颜色变红;</span> <span>2、鼠标在文字上点击时,粒子远离鼠标指针</span> </p> <script> if(drawing1.getContext){ var cxt = drawing1.getContext('2d'); var W = drawing1.width = 300; var H = drawing1.height = 200; var imageData; var dataArr; btnSetText.onclick = function(){ fnSetText(textValue.value); } function fnSetText(str){ cxt.clearRect(0,0,W,H); cxt.textBaseline = 'top'; var sh = 60; cxt.font = sh + 'px 宋体' var sw = cxt.measureText(str).width; if(sw > W){ sw = W; } cxt.fillText(str,(W - sw)/2,(H - sh)/2,W); imageData = cxt.getImageData(0,0,W,H); dataArr = setData(imageData,1,1); } fnSetText('小火柴'); btnchoose1.onclick = function(){ dataArr = setData(imageData,1,1); saveData(dataArr); } btnchoose2.onclick = function(){ dataArr = setData(imageData,2,1); saveData(dataArr); } btnchoose3.onclick = function(){ dataArr = setData(imageData,1,2); saveData(dataArr); } //筛选粒子 function setData(imageData,n,m){ //从imageData对象中取得粒子,并存储到dots数组中 var dots = []; //dots的索引 var index = 0; for(var i = 0; i < W; i+=n){ for(var j = 0; j < H ;j+=n){ //data值中的红色值 var k = 4*(i + j*W); //data值中的透明度 if(imageData.data[k+3] > 0){ //将透明度大于0的data中的红色值保存到dots数组中 dots.push(k); dots[index++] = { 'index':index, 'x':i, 'y':j, 'red':k, 'green':k+1, 'blue':k+2, 'randomX':Math.random()*W, 'randomY':Math.random()*H, 'mark':false } } } } //筛选粒子,仅保存dots.length/m个到newDots数组中 var newDots = []; var len = Math.floor(dots.length/m); for(var i = 0; i < len; i++){ newDots.push(dots.splice(Math.floor(Math.random()*dots.length),1)[0]); } return newDots; } function saveData(dataArr){ //将筛选后的粒子信息保存到新建的imageData中 var oNewImage = cxt.createImageData(W,H); for(var i = 0; i < dataArr.length; i++){ for(var j = 0; j < 4; j++){ oNewImage.data[dataArr[i].red+j] = imageData.data[dataArr[i].red+j]; } } //写入canvas中 cxt.putImageData(oNewImage,0,0); } //显示粒子 function showData(arr,oTimer,index,n){ oTimer = setTimeout(function(){ cxt.clearRect(0,0,W,H); //写入canvas中 saveData(arr[index++]); if(index == n){ clearTimeout(oTimer); }else{ //迭代函数 showData(arr,oTimer,index,n); } },60); } //重新混乱 function showDataToRandom(dataArr,oTimer,n){ oTimer = setTimeout(function fn(){ cxt.clearRect(0,0,W,H); for(var i = 0; i < dataArr.length; i++){ var temp = dataArr[i]; var x0 = temp.x; var y0 = temp.y; var disX = temp.randomX - temp.x; var disY = temp.randomY - temp.y; cxt.fillRect(x0 + disX/n,y0 + disY/n,1,1); } n--; if(n === 0){ clearTimeout(oTimer); }else{ showDataToRandom(dataArr,oTimer,n); } },60); } //混乱聚合 function showRandomToData(dataArr,oTimer,n){ oTimer = setTimeout(function(){ cxt.clearRect(0,0,W,H); for(var i = 0; i < dataArr.length; i++){ var temp = dataArr[i]; var x0 = temp.randomX; var y0 = temp.randomY; var disX = temp.x - temp.randomX; var disY = temp.y - temp.randomY; cxt.fillRect(x0 + disX/n,y0 + disY/n,1,1); } n--; if(n === 0){ clearTimeout(oTimer); }else{ showRandomToData(dataArr,oTimer,n); } },60); } btn1.onclick = function(){ btn1.arr = []; for(var i = 10; i > 1; i--){ btn1.arr.push(setData(imageData,i,1)); } showData(btn1.arr,btn1.oTimer,0,9); } btn2.onclick = function(){ btn2.arr = []; for(var i = 10; i > 0; i--){ btn2.arr.push(setData(imageData,2,i)); } showData(btn2.arr,btn2.oTimer,0,10); } btn3.onclick = function(){ clearTimeout(btn3.oTimer); showRandomToData(dataArr,btn3.oTimer,10); } btn4.onclick = function(){ clearTimeout(btn4.oTimer); showDataToRandom(dataArr,btn4.oTimer,10); } //鼠标移动 drawing1.onmousemove = function(e){ e = e || event; var x = e.clientX - drawing1.getBoundingClientRect().left; var y = e.clientY - drawing1.getBoundingClientRect().top; cxt.beginPath(); cxt.arc(x,y,10,0,Math.PI*2); for(var i = 0; i < dataArr.length; i++){ var temp = dataArr[i]; if(cxt.isPointInPath(temp.x,temp.y)){ cxt.fillStyle = 'red'; cxt.fillRect(temp.x,temp.y,1,1); } } cxt.fillStyle = 'black'; } //鼠标点击 drawing1.onmousedown = function(e){ var r = 20; e = e || event; var x = e.clientX - drawing1.getBoundingClientRect().left; var y = e.clientY - drawing1.getBoundingClientRect().top; cxt.beginPath(); cxt.arc(x,y,r,0,Math.PI*2); for(var i = 0; i < dataArr.length; i++){ var temp = dataArr[i]; if(cxt.isPointInPath(temp.x,temp.y)){ temp.mark = true; var angle = Math.atan2((temp.y - y),(temp.x - x)); temp.endX = x - r*Math.cos(angle); temp.endY = y - r*Math.sin(angle); var disX = temp.x - temp.endX; var disY = temp.y - temp.endY; cxt.fillStyle = '#fff'; cxt.fillRect(temp.x,temp.y,1,1); cxt.fillStyle = '#f00'; cxt.fillRect(temp.endX,temp.endY,1,1); cxt.fillStyle="#000"; dataRecovery(10); }else{ temp.mark = false; } } var oTimer = null; function dataRecovery(n){ clearTimeout(oTimer); oTimer = setTimeout(function(){ cxt.clearRect(0,0,W,H); for(var i = 0; i < dataArr.length; i++){ var temp = dataArr[i]; if(temp.mark){ var x0 = temp.endX; var y0 = temp.endY; var disX = temp.x - x0; var disY = temp.y - y0; cxt.fillRect(x0 + disX/n,y0 + disY/n,1,1); }else{ cxt.fillRect(temp.x,temp.y,1,1); } } dataRecovery(n-1); if(n === 1){ clearTimeout(oTimer); } },17); } } } </script> </body> </html>
以上がキャンバスパーティクルシステムの構築方法を詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。