Home >Web Front-end >JS Tutorial >Detailed explanation of the construction method of canvas particle system
The following editor will bring you a detailed explanation of the construction of canvas particle system. The editor thinks it’s pretty good, so I’ll share it with you now and give it as a reference. Let’s follow the editor and take a look.
What we said before
This article will start with the most basic theoretical knowledge of the imageData object. In detail Introducing the construction of the canvas particle system
imageData
There are three methods for image data, including getImageData(), putImageData(), createImageData ()
【getImageData()】
2D context can obtain the original image data through getImageData(). This method receives 4 parameters: the x and y coordinates of the screen area and the pixel width and height of the area
For example, to obtain the area with the coordinates of the upper left corner (10,5) and a size of 50*50 pixels For image data, you can use the following code:
var imageData = context.getImageData(10,5,50,50);
The returned object is an instance of ImageData. Each ImageData object has 3 properties: width\height\data
1. Width: Represents the width of the diagonal of imageData.
2. Height: Represents the height of the imageData object.
3. Data is an array that stores the data of each pixel in the image. . In the data array, each pixel is stored with 4 elements, representing red, green, blue, and transparency respectively.
[Note] How many pixels there are in the image, the length of data is equal to the number of pixels multiplied by 4
//第一个像素如下 var data = imageData.data; var red = data[0]; var green = data[1]; var blue = data[2]; var alpha = data[3];
The value of each element in the array is between 0-255. If you can directly access the original image data, you can operate these data in various ways
[Note] If the canvas to be obtained using getImageData() contains the drawImage() method, the URL in this method cannot cross domain
[createImageData()]
The createImageData(width,height) method creates a new blank ImageData object. The default pixel value of the new object is transparent black, which is equivalent to rgba(0,0,0,0)
var imgData = context.createImageData(100,100);
【putImageData()】
putImageData( ) method puts the image data back onto the canvas from the specified ImageData object. This method has the following parameters
imgData:要放回画布的ImageData对象(必须) x:imageData对象的左上角的x坐标(必须) y:imageData对象的左上角的y坐标(必须) dirtyX:在画布上放置图像的水平位置(可选) dirtyY:在画布上放置图像的垂直位置(可选) dirtyWidth:在画布上绘制图像所使用的宽度(可选) dirtyHeight:在画布上绘制图像所使用的高度(可选)
[Note] Parameters 3 to 7 are either none or all exist.
context.putImageData(imgData,0,0);
context.putImageData(imgData,0,0,50,50,200,200);
Particle writing
particles refer to each pixel in the image data imageData. The following is a simple example to illustrate complete writing and particle writing
[Full writing]
The text 'Little Match' exists in canvas1 of 200*200, and the entire canvas1 is used as an image Write data into canvas2 of the same size
<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>
[Particle writing]
For complete writing, it is equivalent to a simple copy and paste. If you want fine control over each pixel, you need to use particle writing. There are a lot of blank areas in canvas1, and only the area with the words 'little match' is valid. Therefore, the particles can be filtered based on the transparency in the image data imageData, and only particles with a transparency greater than 0 are filtered out.
<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>
Although the results look the same, canvas2 only uses 2336 out of 40000 particles in canvas1
Particle Filter
When the particles are fully written, the effect is the same as canvas copy and paste. When the particles are filtered, some wonderful effects will appear
【Sequential filtering】
Because when obtaining the particles, a double loop of width value * height value is used, and All increase by 1. If instead of adding 1, you add n, you can achieve the effect of sequential filtering
<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>
[Random filtering]
In addition to using sequential filtering, Random screening can also be used. The position information of the particles obtained through the double loop is placed in the dots array. Filter through the splice() method, put the filtered position information into the newly created newDots array, and then use createImageData() to create a new image data object and return
<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>
Pixel display
Let’s use particle filtering to achieve the effect of a pixel display. Pixel display means gradually transitioning from unclear effect to full display
[Sequential pixel display]
The implementation principle of sequential pixel display is very simple. For example, there are 2000 particles in total. , a total of 10 levels of transition effects. Then use 10 arrays to store 200, 400, 600, 800, 100, 1200, 1400, 1600, 1800 and 2000 particles respectively. Then use the timer to display it gradually
<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>
【Random pixel display】
The principle of random pixel display is similar, save multiple Arrays of different numbers of random pixels can
<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>
Particle Animation
粒子动画并不是粒子在做动画,而是通过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>
The above is the detailed content of Detailed explanation of the construction method of canvas particle system. For more information, please follow other related articles on the PHP Chinese website!