canvas 文字粒子特效
1k4WEBcanvas2019-04-23

一个文字粒子效果,折腾了一些时间,纯粹觉得好玩 点这里看效果

随机初始化部分粒子

1、首先要明白每个粒子都是一个对象,都有自己的起点,移动速度,移动轨迹,终点

2、粒子活动轨迹:初始化 —- 聚合拼合文字形状 —- 散开 —- 再聚合 —- 散开…

3、根据动画时间调整粒子的移动速度来安排他们的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Point{
constructor() {
this.startx = Math.floor(Math.random() * docsize[0]), // 初始起点
this.starty = Math.floor(Math.random() * docsize[1]),
this.speedx = (Math.random() * 2 - 1) * pointspeed, // 移动速度
this.speedy = (Math.random() * 2 - 1) * pointspeed,
this.endx = 0, // 终点
this.endy = 0,
this.color = Math.floor(Math.random() * 5) // 粒子颜色
}
endpoint(x, y) {
this.endx = x
this.endy = y
}
animal() {
this.startx += this.speedx
this.starty += this.speedy

// 到达边界改变粒子运动方向
this.speedx *= this.startx > docsize[0] || this.startx < 0 ? -1 : 1
this.speedy *= this.starty > docsize[1] || this.starty < 0 ? -1 : 1

// 调整点的移动速度用以聚和拼合文字
if(time === 100 || time === 600 || time === 1100) {
this.speedx = (this.endx - this.startx) / joinspeed
this.speedy = (this.endy - this.starty) / joinspeed
}

// 到达终点后静止不动
if(time === 100 + joinspeed || time === 600 + joinspeed || time === 1100 + joinspeed) {
this.speedx = 0
this.speedy = 0
}

// 散开
if(time === 300 || time === 800) {
this.speedx = (Math.random() * 2 - 1) * pointspeed
this.speedy = (Math.random() * 2 - 1) * pointspeed
}

maincontent.beginPath()
maincontent.fillStyle = color[this.color]
maincontent.arc(this.startx, this.starty, 7, 0, Math.PI * 2)
maincontent.fill()
}
}

使用 canvas 画板生成文字

1
2
3
4
5
6
7
8
// 【文字面积,循环时用于判读y轴高度,粒子大小间隔, 文字宽度】
let [imgdata, cyclic, size, textwith] = [{}, 1, 16, 0]

textcontext.font = "normal 900 " + fontsize +"px Avenir, Helvetica Neue, Helvetica, Arial, sans-serif"
textwith = Math.floor(textcontext.measureText(text).width)
textcontext.fillStyle = '#ff0000'
textcontext.fillText(text, (docsize[0] - textwith) / 2, (docsize[1]) / 2)
textwith = ~~ (textwith) * size + size

遍历 imageData 获取文字区域的像素坐标

不了解 imagedata 怎么用? 看看这篇文章canvas 的 imagedata 对象

获取坐标这里有很多种方法,我看了一些教程好像没人像我这么写,要注意的是

  • imageData 4个元素为一个像素,也就是一个R G B A 值,A 是 alpha 透明度

  • 空白的区域 rgba 就是 0,0,0,0 , 文字区域就是有颜色的如果你没有设置字体颜色默认是黑色 rgba 就是 0,0,0,255,通过判断第四个元素可以获取文字区域

  • 但是我建议重新设置一个其他的颜色比如红色 255,0,0,255,用第1个和2个数字来判断, 这样字体边缘会圆滑些,因为在字体边缘黑色和白色的交界处可能有某几个像素不是透明的

  • 每个坐标最后都会生成一个圆,所以这里获取的是圆心的坐标,圆之间还需要留有空隙,所以遍历的时候你要根据你的圆的大小掌握好间隔

  • 获取文字区域粒子数量后需要判断,目前屏幕上现有的粒子是否足够拼合和文字或者是否还需再添加粒子

  • 确定粒子数量后再将文字坐标作为粒子移动终点赋值给粒子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // 获取文字所在区域,尽可能减小面积
    imgdata = textcontext.getImageData(0,0, textwith, fontsize * 2)
    textcontext.clearRect(0, 0, docsize[0], docsize[1])

    // 粒子圆心坐标,粒子数组
    let [x, y, len] = [0, 0, 0]

    // 遍历data数据查找文字所在的坐标
    for (var i = 0; i < imgdata.data.length; i += size * 4) {
    if (imgdata.data[i] === 255 && imgdata.data[i+3] === 255) {

    // 判断当前粒子数量是否能够拼合文字
    if (len > pointarr.length - 1) pointarr.push(new Point)

    // 获取每个粒子聚拢的终点
    pointarr[len].endpoint(i /4 % textwith, cyclic)
    len ++
    }
    if (i/4 == cyclic * textwith) {
    cyclic += size
    i = textwith * (cyclic-1) * 4

    }

    pointarr.length - 1 - len > 0 ? pointarr.splice(len, pointarr.length - len) : ''

不用 imagedata, 遍历 width 和 height 也是一样的

1
2
3
4
5
6
7
for (let y = 0; y < textCanvas.height; y += size) {
for (let x = 0; x < textwith; x += size) {
if (imageData.data[(x + y * textwith) * 4] > 0) {
// todo....
}
}
}

源码带有详细的注解点这儿

更多效果
开源不易,觉得还不错点个 start 吧 (´▽`ʃ♡ƪ)