基于canvas使用粒子拼出你想要的文字[2]——粒子的动画效果

2,902 阅读5分钟

写在最前

本次分享一个用canvas粒子渲染文字的“完整”版实现,功能包括:随机粒子缓动动画,粒子汇聚、发散,并拼出你想要的文字。本文继续上面一节基于canvas使用粒子拼出你想要的文字的基础效果,完善了在文字拼接过程中的粒子效果。

欢迎关注我的博客,不定期更新中——

上节回顾

自上次的分享基于canvas使用粒子拼出你想要的文字,我们实现了一个可配置的用粒子拼出想要的文字效果,不过这个效果是静态的,就像这样:
image

这次我们试图对它进行了一些完善,让其可以尽量完整的实现我们的诉求。

效果图

惯例直接看下效果图:

2017-12-18 14_38_18

这是一个事先配置好的动画效果用来展示一下粒子的完整运动轨迹。在这个例子中我们做了以下几件事:

  • 初始化一定数量的粒子,开始缓动
  • 监听到输入事件后,拼出相应文字。其拼接的粒子是从缓动中的随机粒子抽取,如果不够则新加粒子
  • 当再次监听到输入事件后,原文字散开到随机位置,新文字按照上一步进行绘制,以此往复

故本次我们讨论的重点则围绕缓动动画与粒子的汇聚与散开进行展开

整体逻辑

先来分析一下整体的实现思路

1、首先为了增加粒子的重用性,不需要每次拼新的文字都new一堆新的粒子。故选择维护了两个数组进行存放相应粒子。即随机缓动数组与展示效果数组

2、初始化一定数目粒子,粒子位置随机,半径随机,加入到随机粒子数组

3、对加入到随机粒子数组中的对象执行缓动动画

4、监听事件被触发,清空展示粒子数组,将当前页面所有粒子全部加入到随机粒子数组,同时更新粒子状态,让每个粒子重新出现在各个随机位置

5、当拼接文字开始,每次需要绘制一个粒子到拼接的地点时则从随机粒子数组中pop出一个粒子对象,更新粒子的位置等信息,push到展示粒子数组中,如果随机粒子数组数量不够,则新建对象添加到展示粒子数组。

6、展示粒子数组中的粒子收集完毕后,遍历数组依次渲染到指定位置

新的监听事件被触发重复以上的4、5、6步骤

实现核心

  • 随机粒子的缓动动画
  • 从随机粒子变为展示粒子的过程中,需要绘制出粒子的运动轨迹以实现发散与汇聚的效果

缓动动画

就像下面这样:

2017-12-18 17_35_26

观察其中一个粒子的动画行为可以发现缓动动画实现核心点在于:起始速度快,之后速度逐步递减,直至停下

所以速度是与起始点与中点距离相关的,距离越大,速度越快,反之亦然。那么我们的速度就可以表示为:相对路程 :heavy_multiplication_x: 缓动系数(一个小数),即可使每一帧的位移距离从大到小,速度从快至慢。

代码实现:

首先初始化了一些随机的粒子:

for(var i = 0; i < 100; i++) {
    var option = {
        radius: ~~(Math.random() * 3) + 1,
        x: ~~ (Math.random() * canvas.width),
        y: ~~ (Math.random() * canvas.height),
        color: 'rgba(255, 255, 255, 0.5'
    }
    var bubble = new Bubble(option)
    circleArr.push(bubble)
}

之后再绘制粒子缓动:

var dis = ~~ Math.sqrt(Math.pow(Math.abs(this.x - this.randomX), 2) + Math.pow(Math.abs(this.y - this.randomY), 2)),
    ease = 0.05
    ...
 if( dis > 0) {
    //当粒子在向目标点移动的过程中,由缓动系数与距离控制速度
    if(this.x < this.randomX) {
        this.x += dis * ease 
    } else {
        this.x -= dis * ease 
    }
    if(this.y < this.randomY) {
        this.y += dis * ease 
    } else {
        this.y -= dis * ease 
    }
    } else {
    //达到目标点后更新下一个目标点
    this.randomX += ~~(Math.random() * (Math.random() > 0.5 ? 5 : -5) * 2)
    this.randomY += ~~(Math.random() * (Math.random() > 0.5 ? 5 : -5) * 2) 
    }
    ctx.beginPath()
    ctx.arc(this.x, this.y, this.originRadius, 0, 2 * Math.PI, false)
    ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'
    ctx.fill()
}
...

粒子的发散与汇聚

每一个粒子都会经历由随机粒子 => 展示粒子 => 随机粒子的过程,在这其中我们需要控制好,粒子的坐标,由于在文字与随机移动的切换中,均为缓动效果实现。故我们不单单要在每个状态改变的时候维持好下一个状态下该粒子的坐标,还应该同步保持一个坐标的副本。由于需要通过两个状态下的坐标值算出需要移动的距离,并且这个距离是固定的,所以在状态切换的过程中不能改变这两个坐标值,但是粒子时刻在动,故我们需要一个坐标副本来实时表示当前的粒子坐标位置。

...
if(this.isWord) { //如果该粒子当前是文字
    var disLastPosition = ~~ Math.sqrt(Math.pow(Math.abs(this.lastX - this.randomX), 2) + Math.pow(Math.abs(this.lastY - this.randomY), 2))
    var ease = 0.05
    if (disLastPosition > 0) {
        if (this.lastX < this.randomX) {
            this.lastX += disLastPosition * ease
        } else {
            this.lastX -= disLastPosition * ease
        }
        if (this.lastY < this.randomY) {
            this.lastY += disLastPosition * ease
        } else {
            this.lastY -= disLastPosition * ease
        }
    } else {
        this.lastX = this.randomX
        this.lastY = this.randomY
        this.x = this.lastX //更新x,y值
        this.y = this.lastY
        this.isWord = false
    }
    ctx.beginPath()
    ctx.arc(this.lastX, this.lastY, this.originRadius, 0, 2 * Math.PI, false)
    ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'
    ctx.fill()
    return
}
...

此时我们使用lastX,lastY属性来作为前一个坐标值的变量,在计算出位移距离后,通过改变lastX,lastY来实现粒子的动画效果,当粒子到达指定位置后再更新x,y的坐标为新的坐标。

小结

至此我们大体完成了一个完整的粒子文字特效,当然和一些线上炫酷的 :chestnut:不能比,不过大体是那么个意思,代码细节部分有兴趣的同学可以参照源码(见最后),或者自己实现一版玩玩~

其他canvas相关文章

最后

源代码见:这里

惯例po作者的博客,不定时更新中——

有问题欢迎在issues下交流。