使用pixi做粒子聚合文字的效果[1]

说明

创意来自于国外的一款特效,原特效使用png作为聚合的区域。

本次主要是想自己尝试实现这一特效,下文的DEMO中,我使用PIXI.Text创建了文字Sprite,作为粒子聚合的形状。然而这份代码使用的方法,其实不局限于此,使用任何PIXI.DisplayObject作为代码中的shape都是可以的。

这里虽然使用了pixi,但是特效的思路与框架关系不大,使用原生canvas也是可以做到的,这里就不赘述了,Show You My Code:

1
2
<script src="https://cdn.bootcdn.net/ajax/libs/pixi.js/6.0.2/browser/pixi.js"></script>
<div id="preview-box" style="text-align:center"></div>
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// 简单的数组随机取值函数
function random(arr) {
return arr[Math.floor(Math.random() * arr.length)]
}

let
scale = 4, // 舞台放大比例 会影响粒子数量 num; 影响抖动强度 shake;
num = 1000 * scale, // 粒子数量
strArr = ['Hello', 'World', 'I\'am','pixi.js'],
rate = 180, // 帧 60帧 相当于 1s
frame = 0, // 运行的总帧数
times = 0, // 动画的总次数
joinTimes = 0, // 聚合动画的次数
color = '#ff0000',
shake = { x: .7, y: .4 }, // x轴 和 y轴 的抖动强度
game = new PIXI.Application({
width: 120 * scale,
height: 28 * scale,
transparent: true,
antialias: true,
backgroundAlpha: 0,
backgroundColor: 'transparent'
});

// 添加pixi舞台到页面上展示
document.getElementById('preview-box').appendChild(game.view)

// 生成图形 方便后续提取有色位置
// 也可以用png图片作为shape传入 这里就不做演示了
let shape = strArr.map(str => {
let s = new PIXI.Text(str, {
fontSize: 16 * scale,
fontStyle: 'italic',
fontWeight: 'bold',
letterSpacing: 2
})
s.name = str
s.anchor.set(0.5)
s.position.set(game.renderer.width / 2, game.renderer.height / 2)
return s
})

// 生成粒子
let paticles = new PIXI.ParticleContainer(num);
for (let n = 0; n < num; n++) {
let d = new PIXI.Sprite(PIXI.Texture.WHITE)
d.name = n
d.width = 1
d.height = 1
d.anchor.set(Math.random(), Math.random())
d.position.set(game.renderer.width * Math.random(), game.renderer.height * Math.random())
d.dx = d.x
d.dy = d.y
d.alpha = Math.random()
if (typeof color === 'string') d.tint = PIXI.utils.string2hex(color)
if (Array.isArray(color)) {
let c = random(color)
d.tint = PIXI.utils.string2hex(c)
}
paticles.addChild(d)
}
game.stage.addChild(paticles)

// ticker 进行动画执行
PIXI.Ticker.shared.add(() => {

frame++
paticles.children.forEach(d => {
if (d.dx !== d.x) {
d.x += (d.dx - d.x) / 20
}
if (d.dy !== d.y) {
d.y += (d.dy - d.y) / 20
}
// 抖动强度在这里调整
d.x += (Math.random() - 0.5) * shake.x * scale / 2
d.y += (Math.random() - 0.5) * shake.y * scale / 2
})

// 每隔一定帧数做动画
if (frame % rate === 0) {
if (times % 2 === 0) {
console.log(`聚合形状\t${shape[joinTimes % shape.length].name}`)
join(game.renderer, shape[joinTimes % shape.length], paticles)
joinTimes++
} else {
leave(game.renderer, paticles)
}
times++;
}

})

// 聚合动画
function join(renderer, sprite, paticles) {
let { x: ox, y: oy, width, height } = sprite.getBounds();
game.stage.removeChild(game.stage.getChildByName('shape'))

// 提取贴图数据
let shadow = new PIXI.Container()
shadow.addChild(sprite)
let dotData = renderer.extract.pixels(shadow);
shadow.destroy()

// 提取有颜色的点
let colorPos = []
for (let y = 0; y < height; y += 1) {
for (let x = 0; x < width; x += 1) {
let alpha = dotData[y * width * 4 + x * 4 + 3];
if (alpha > 1) {
colorPos.push({
x: ox + x,
y: oy + y
})
}
}
}

// 将所有粒子随机分配到目标点上
paticles.children.forEach(d => {
let pos = random(colorPos)
d.dx = pos.x
d.dy = pos.y
})

// 销毁
dotData = null
colorPos = null
}

// 离散
function leave(renderer, paticles) {
paticles.children.forEach(d => {
d.dx = renderer.width * Math.random()
d.dy = renderer.height * Math.random()
})
}
  1. 说明