打造高大上的Canvas粒子动画

LouanneY23 7年前
   <p>首先来看下我们准备要做的粒子动画效果是怎么样的~</p>    <p>是这样:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/3ea19f1be5f0aa4cdd77aae5bc15fc5d.gif"></p>    <p>或者是这样:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/ffa491ebf92e9cc91b9b393aa6fce9ac.gif"></p>    <p>甚至是这样:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/d945c33f22e7580ff11b3deee33cd67d.gif"></p>    <p>很酷炫!</p>    <p>那如何去实现类似上面的粒子动画甚至根据自己的喜好去做更多其他轨迹的动画呢~请看下面详细的讲解。</p>    <p><strong>技术选择</strong></p>    <p>因为粒子数量很多,而且涉及到图像像素处理,所以这里使用 <strong>Canvas</strong> 是不二选择。</p>    <p>注意,以下演示的代码只是关键代码,重点在于解决思路。</p>    <h3><strong>一、绘制粒子轮廓图</strong></h3>    <p>首先要在canvas画布上绘制一个由粒子组成的轮廓图,记录下每一个粒子的坐标,这样才能有后续的动画。</p>    <p><strong>1. 创建一个<canvas>元素,并获取Canvas画布渲染上下文</strong></p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/ca53339103c8f2930c82275478c8d56d.png"></p>    <p>< canvas>是一个双标签元素,通过width和height的值来设置画布的大小。至于ctx(画布渲染上下文),可以理解为画布上的画笔,我们可以通过画笔在画布上随心所欲的绘制图案。如果浏览器不支持canvas会直接显示<canvas>标签中间自己设定的文字。当然 <canvas> 标签中间也可以是一张当不支持canvas时需要替换显示的图片。</p>    <p><strong>2. 使用canvas的图像操作API绘制图像</strong></p>    <p>绘制图像的关键API及参数说明:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/b75756da9753ee3872fbe2e3039f7501.png"></p>    <p>引用MDN上的一张图会比较清晰的看出每个参数的作用:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/79a705cb7e3d6335f21b270f6e81a493.jpg"></p>    <p>drawImage就是把一个image对象或者canvas上(甚至是video对象上的的每一帧)指定位置和尺寸的图像绘制到当前的画布上。而在我们的需求中,是要把整个图像绘制到画布中。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/c7d29983c16d04987f1277dde01eeda1.png"></p>    <p>对应浏览器看到的 <a href="/misc/goto?guid=4959724812999625203" rel="nofollow,noindex">效果</a> :</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/4f8422651b76927b2a403a52e4f01695.png"></p>    <p><strong>3. 获取图像的像素信息,并根据像素信息重新绘制出粒子效果轮廓图</strong></p>    <p>canvas有一个叫getImageData的接口,通过 该接口可以获取到画布上指定位置的全部像素的数据:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/a014dfa175819831e14970bed83d93be.png"></p>    <p>把获取的imageData输出到控制台可以看到,imageData包含三个属性:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/d3cce5f1e787af2074aeb2fae89c3972.png"></p>    <p>其中,width、height是读取图像像素信息完整区域的宽度和高度,data是一个Uint8ClampedArray类型的一维数组,包含了整个图片区域里每个像素点的RGBA的整型数据。这里必须要理解这个数组所保存像素信息的排序规则,请看下图描述的data数组:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/145213a3185421e6b80bdcf46ec8d0a5.png"></p>    <p>每一个色值占据data数组索引的一个位置,一个像素有个4个值(R、G、B、A)占据数组的4个索引位置。根据数列规则可以知道,要获取第n个位置(n从1开始)的R、G、B像素信息就是:Rn = (n-1)*4 ,G n = (n-1)*4+1 ,B n = (n-1)*4+2  ,so easy~  当然,实际上图像是一个包括image.height行,image.width列像素的矩形而不是单纯的一行到结束的,这个n值在矩形中要计算下:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/14896b24321291931f6396dde2efa53b.png"></p>    <p>由于一个像素是带有4个索引值(rgba)的,所以拿到图像中第i行第j列的R、G、B、A像素信息就是 Rij = [(j-1)*imageData.width + (i-1)]*4 ,G ij = [(j-1)*imageData.width + (i-1)]*4 + 1, B ij = [(j-1)*imageData.width + (i-1)]*4 + 2,A ij = [(j-1)*imageData.width + (i-1)]*4 + 3  。每个像素值都可以拿到了!</p>    <p>接下来就要把图像的粒子化轮廓图画出来了。那么,怎么做这个轮廓图呢,我们先读取每个像素的信息(用到上面的计算公式),如果这个像素的色值符合要求,就保存起来,用于绘制在画布上。另外,既然是做成粒子的效果,我们只需要把像素粒子保存一部分,展示在画布上。</p>    <p>具体做法是,设定每一行和每一列要显示的粒子数,分别是cols和rows,一个粒子代表一个单元格,那么每个单元格的的宽高就是imageWidth/cols和imageHeight/rows,然后循环的判断每个单元格的第一个像素是否满足像素值的条件,如果满足了,就把这个单元格的坐标保存到数组里,用作后续绘制图案用。</p>    <p>关键参考代码:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/bcd2b488be0aea873d8145401737420d.png"></p>    <p>用完整代码做出的demo及 效果 :</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/47c0f62f619e2071abea8a262200c340.png"></p>    <p>至此,粒子轮廓图已经制作完成。</p>    <h3><strong>二、制作粒子动画</strong></h3>    <p>制作粒子动画分两种:</p>    <p>一种是粒子漂浮类,这种比较简单,只需要随机的改变每个粒子的位置值,然后一直执行setInterval或者requestAnimationFrame重绘画布即可,具体的效果因人喜好而去设定,就不具体讲解了,做了个简单的粒子漂浮的 例子 。</p>    <p>另一种是粒子的轨迹动画,这个相对复杂一些。这里要介绍的是每个粒子按照指定的轨迹在指定的时间内做位移,最终汇聚成指定图案的动画效果(也就是文章一开始的动效),要做成这类动画效果需要解决两个问题:一个是动画轨迹,另外一个是每个粒子执行动画的时机。</p>    <p><strong>粒子动画轨迹</strong></p>    <p>动画位移的轨迹,最常见的就是单位时间内改变固定的位移值,从而达到动画效果。但要做到炫酷的效果依赖这种单调固定的位移肯定是不行的。所以位移可以依赖缓动函数去做到单位时间内改变不一样的位移值,从而达到特别的效果。</p>    <p><strong>制作缓动效果有两种方法:</strong></p>    <p>一种是自己设定好控制点,然后通过贝塞尔曲线公式来计算每个单位时间的坐标值。</p>    <p>引用了 wikipedia 里面的图:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/e40165c7c4d059db8c913daecf337d85.gif"></p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/45d35cb4e1b446501fcefac07b3dab55.gif"></p>    <p>上面两个图都是在绘制一条特定曲线,可以看出二次曲线需要一个特定控制点P1,三次曲线需要两个特定控制点P1和P2来确定一条曲线,高阶曲线甚至需要更多的控制点来确定曲线轨迹。</p>    <p>求曲线的公式是根据 德卡斯特里奥算法 计算得来的,直接上公式。</p>    <p>二次曲线对应的公式:</p>    <p><img src="https://simg.open-open.com/show/f3f28715ddb2dd3d2494c3d55c15daad.png"></p>    <p>三次曲线对应的公式:</p>    <p><img src="https://simg.open-open.com/show/63294f710e4689566a725cf172cfdcb6.png"></p>    <p>从公式可以看出,只要确定控制点坐标、起始坐标和终点坐标后,就可以确定了一条曲线,然后就可以根据曲线公式求出每个时刻t对应的位置值B(t)。</p>    <p>当然使用这种方法需要自己去制定控制点坐标,计算也比较复杂,实现起来很繁琐。没事,我们还有别的办法确定曲线。</p>    <p>另外一种方法就是使用已有的缓动函数,不需要自己制定控制点,这里推荐出名的Tween算法的 缓动函数 ,用其中一个缓动函数来介绍下参数值,其他缓动函数所传的参数值是一样的:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/2152993a2e2d9dd93e1e51f14833273b.png"></p>    <p>是不是觉得很熟悉?对没错,jquery用的动画扩展插件 easing.js 就是Tween算法的缓动函数。有了这现成的缓动函数,就可以制定粒子的起始点、终点(终点就是图案本身的坐标位置)以及动画执行持续时间来做我们要的 效果。</p>    <p>关键参考代码:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/eba4ec0ce4a906c083edb080b3b70940.png"></p>    <p>根据参考代码做出一个 效果 :</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/3bd7ef1471857bb08d6f345a6dfe0a1b.gif"></p>    <p>嗯,动画效果是有了,但总感觉不太对劲。。。唔,仔细观察一下,是图案动画执行太过整体了,没有明显的颗粒动画效果,这就引出粒子动画的另一个关键点,粒子执行动画的时机。</p>    <p><strong>粒子执行动画的时机</strong></p>    <p>要让粒子效果比较明显,那就不能让动画效果执行太过整体了,需要让图案上每个粒子有不同的时间间隔启动,根据一定的规律交错的执行动画。这里的粒子启动间隔有两种,一种是每一行粒子执行时间间隔,要让每一行的粒子启动时间有规律错开;另外一种是每一行粒子之间启动时间随机的错开,这样执行的粒子动画才会有一种层次感和每个粒子有独立动画的颗粒感。看下加了粒子启动时间间隔之后的 效果 对比 :</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/3e4f9d1090f72756b8b1eceaee03b700.gif"></p>    <p>比上面不加粒子启动时间间隔的效果好多了。</p>    <p>嗯,介绍差不多就是这样了,如果上面介绍的方法还是解决不了问题的话,还有办法。。。我把粒子动画效果和Tween的缓动函数一起封装了一下。直接配置一下就可以用了。 用法就是创建一个带有id的canvas,设定好宽度和高度,引入 particle.min.js ,然后配置一下参数即可,  demo :</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/d0bc809fbde9fed738034d3af9476625.png"></p>    <p> </p>    <p>来自:https://isux.tencent.com/canvas-particle-animation.html</p>    <p> </p>