代码文件git
每周一点canvas动画系列文章目前已经更新了12篇,今天给你们发个福利。咱们使用canvas来制做一个小的效果。这个小效果是我从codePen上看到的,我对其作了些修改加强,添加了一些新的功能。UI界面就以下图中看到的样子。咱们要实现的效果就如我在图中操做的那样,在输入框中输入文字(无论中文,仍是英文,仍是各类表情也好)均可以在canvas画布中经过众多的粒子组成,在侧边栏中还有不少控件,它们能够控制粒子的各方面属性,以此来造成各类不一样的绚丽效果。github
UI界面的组成很简单,主要有侧边栏控制台
和canvas画布
两部分组成canvas
<canvas id="canvas"></canvas> <div id="control">...</div>
在侧边栏中有一系列的控制条,他们控制着粒子的各类属性,包括文字输入框:数组
<input type="text" id="message" value="hahaha" onchange="change()">
控制条bash
<input type="range" id="gra" value="0" min="-1" max="1" step="0.1" onchange="changeV()">
粒子选择ide
<p style="margin: 0 0 20px 10px;"> <span id="ball">圆形</span> <span id="rect">方块</span> </p>
在这我就不一一列举了!CSS样式文件主要是对UI界面的布局和样式处理,具体请查看代码文件。函数
当点击菜单按钮时,侧边栏滑出,再次点击缩回。采用classList
来切换滑出和缩回的class,在sidebar.js
中布局
var btn = document.getElementById("btn"); var control = document.getElementById("control"); btn.addEventListener('click', function(e){ control.classList.toggle("slide"); }, false)
这样咱们的基础界面就搭建完成。下面就到了咱们这个动画的核心思想字体
首先,咱们在咱们的index.js
文件中定义咱们须要的一些变量动画
var canvas = document.getElementById('canvas'); context = canvas.getContext('2d'); W = canvas.width = window.innerWidth; H = canvas.height = window.innerHeight; gridY = 7, gridX = 7; type = "ball"; var message = document.getElementById('message'), gravity = document.getElementById('gra'), duration = document.getElementById('dur'), speed = document.getElementById('speed'), radius = document.getElementById('rad'), resolution = document.getElementById('res'); graVal = parseFloat(gravity.value); durVal = parseFloat(duration.value); spdVal = parseFloat(speed.value); radVal = parseFloat(radius.value); resVal = parseFloat(resolution.value); colors = [ '#f44336', '#e91e63', '#9c27b0', '#673ab7', '#3f51b5', '#2196f3', '#03a9f4', '#00bcd4', '#009688', '#4CAF50', '#8BC34A', '#CDDC39', '#FFEB3B', '#FFC107', '#FF9800', '#FF5722' ]; function change(){ 。。。 } function changeV() { 。。。 } (function drawFrame(){ window.requestAnimationFrame(drawFrame, canvas); context.clearRect(0, 0, W, H); 。。。 }())
注意这里的的context, W, H等咱们定义的是全局变量。
这里有两个变量可能你不知道他是干什么的gridX
和gridY
,以后我会详细介绍。
这个文件是咱们整个动画效果的核心,只有理解了它,你才能了解这个效果的实现原理。由于不是很长,这里我把文件所有列出:
function Shape(x, y, texte){ this.x = x; this.y = y; this.size = 200; this.text = texte; this.placement = []; } Shape.prototype.getValue = function(){ context.textAlign = "center"; context.font = this.size + "px arial"; context.fillText(this.text, this.x, this.y); var idata = context.getImageData(0, 0, W, H); var buffer32 = new Uint32Array(idata.data.buffer); for(var j=0; j < H; j += gridY){ for(var i=0 ; i < W; i += gridX){ if(buffer32[j * W + i]){ var particle = new Particle(i, j, type); this.placement.push(particle); } } } context.clearRect(0, 0, W, H); }
接下来,我就详细的解一下该文件的代码!首先咱们新建了一个构造函数Shape
,该构造函数有3个参数:
x , y: 要绘制的文字的位置
texte: 要绘制的文字
咱们设置了文字的大小为200px, 而且定义了一个属性placement
,这个属性是一个数组。so wired!它是干什么的呢?别急,继续往下走。
接下来咱们在原型对象上定义了一个方法getValue
.这几行代码:
context.textAlign = "center"; context.font = this.size + "px arial"; context.fillText(this.text, this.x, this.y);
很简单,设置文字对其方式,字体大小,而且经过fillText()
在canvas上绘制文字。若是此时在控制栏中输入文字,在index.js中新建一个shape对象,并把文字传入,再调用getValue方法就能够看到你已经把输入的文字绘制到了canvas中,固然这时候忽略下面的代码啊!
回到正题,接下来咱们调用了context.getImageData()
,它是canvas绘制图片的API接口,经过它咱们能够获得须要绘制的图片的数据内容。也许你会问,它是用来获取canvas上绘制的图片的数据内容,但是咱们这并无绘制图片啊?
其实,该方法的做用并不仅是局限于获取图片的内容。只要canvas上有内容,不论是绘制的文字,仍是图形它都能获取,甚至是空白的canvas它也能获取,只不过此时的数据都是0。
那么经过该API获取的内容是什么样的呢?首先,咱们尝试获取一张空canvas的内容
var canvas = document.getElementById('canvas'), context = canvas.getContext('2d'); var imgData = context.getImageData(0, 0, canvas.width, canvas.height); console.log(imgData);
结果以下:
咱们看到,这里的imgData
是一个对象,该对象的第一个属性就是data
,是一个8位无符号整数的类型化数组Uint8ClampedArray。打开data看看都有什么,这里我随便打开其中的一个看看。
由于canvas为空,因此数据都为零。如今咱们换一下,在canvas中画一个蓝色的矩形。
context.fillStyle = "#49f"; context.fillRect(0, 0, canvas.width,canvas.height); var imgData = context.getImageData(0, 0, canvas.width, canvas.height); console.log(imgData);
看看,咱们的的数据是否是不为零了!OK! 原理我在这解释的都差很少了,咱们回到正题,看下一行代码。
var buffer32 = new Uint32Array(idata.data.buffer);
idata.data.buffer
,在这里咱们调用Uint8ClampedArray对象的buffer属性,获取此数组引用的 ArrayBuffer
。而后将它传入Uint32Array对象(32位无符号整数值的类型化数组)。此时,咱们看看上面绘制蓝色矩形的数据变成什么样了,首先数组长度变为[160000],恰好是上面的8位的四分之一
内容变为
至关于咱们把一张图片的分辨率缩小了,之前有640000个数据, 如今只有160000个数据。固然,在本文中数据的内容不是咱们所关心的。咱们所关心的是在哪有数据。
因此,接下来,就是在有数据的地方,放上咱们的粒子
for(var j=0; j < H; j += gridY){ for(var i=0 ; i < W; i += gridX){ if(buffer32[j * W + i]){ var particle = new Particle(i, j, type); this.placement.push(particle); } } } context.clearRect(0, 0, W, H); //清除所画内容
咱们遍历整个canvas, 经过buffer32[j * W + i]
来判断这个位置的数据是否为空,若是不为空,那么,在这绘制一个粒子。粒子的位置为(i,j)咱们做为参数传入。固然你也能够在数据为空的地方放上粒子,看看会出现什么样的效果。
这里用到了gridX和gridY,它们的做用是来判断每一个多少个距离取一次数据。学过信号抽样的同窗应该很好理解,若是你间隔大,抽样获得的数据就小,反之若是你设定的间隔小,那么抽到的数据就多。在咱们的效果中,咱们绘制的是文字,一样的道理,间隔小获取的数据就多,粒子就多,组成的文字就完整。间隔大获取的就少。那么粒子组成的文字就不那么完整,这两个变量的值,经过分辨率控件来绑定。思来想去仍是上张图吧!
该文件就是咱们的粒子文件,我就不作过多解释了,不懂得欢迎提问。
粒子切换的代码在slide.js
中,很简单,就是绑定了两个事件。
/粒子切换 var ball = document.getElementById("ball"); var rect = document.getElementById("rect"); function chose(particleName){ particleName.addEventListener('click', function(e){ this.style.backgroundColor = "orange"; (particleName == ball ? rect : ball).style.backgroundColor = "rgba(0, 0, 0, 0)"; type = (type === "ball" ? "rect" : "ball"); change(); }, false) } chose(ball); chose(rect);
Ok!这个效果的关键点,基本都已经讲完了,有兴趣本身看看吧!!!