使用pixi做粒子聚合文字的效果[3] - 恐怖字幕

说明

如果基于青草效果再做一点调整,再加上一点夸张的抖动,就会出现老电视受到信号干扰的效果,就像恐怖片里常出现的场景。

取消了随机角度,加上了夸张的抖动。我这里做的效果是横向的长粒子,左右抖动较大。当然这个也可以反过来

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
// 简单的数组随机取值函数
function random(arr) {
return arr[Math.floor(Math.random() * arr.length)]
}

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

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

// 生成图形 方便后续提取有色位置
// 也可以用png图片作为shape传入 这里就不做演示了
let shape = strArr.map(str => {
let s = new PIXI.Text(str, {
fontSize: 24 * 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 = 7
d.height = 1
d.anchor.set(0)
// d.angle = Math.random() * 360
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 进行动画执行
join(game.renderer, shape[joinTimes % shape.length], paticles)
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) {
joinTimes++
times++;
join(game.renderer, shape[joinTimes % shape.length], paticles)
}

})

// 聚合动画
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. 说明