三角填充动画

前言

关于这个特效应该叫什么,暂时也没想好,看到过差不多的特效,然后想了想其中的原理,发现很有意思。

为了让特效的进行过程中降低cpu的消耗,我提前生成了屏幕所需的所有三角形,为每个三角做了坐标标记。其中的问题就是,三角形的排列是错落的,如何判断下一次“行进”的坐标是关键。

因为我只用了一个canvas绘制的三角素材,那么对于每一”行”而言,其实四个三角是一次循环。

  • 第一个三角 不旋转,不偏移
  • 第二个三角,旋转180°,偏移一半的素材高度
  • 第三个三角,不旋转,偏移一半的素材高度
  • 第四个三角,旋转180°,不偏移

最终会如下图排列:

WX20210705-153620@2x

由此可以看出,这四个三角,每个与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
// rt: 右上
// rd: 右下
// lt: 左上
// ld: 左下
// l: 左
// r: 右
// 每一列的移动特征不一样
// 0列 2列 三角向右, 方向有: 右上/右下/左 三种
// 其中,0列和2列,右上的三角的坐标计算方式又有区别
let step = {
0: {
rt: [1, -1],
rd: [1, 0],
l: [-1, 0]
},
2: {
rt: [1, 0],
rd: [1, 1],
l: [-1, 0]
},
1: {
lt: [-1, 0],
ld: [-1, 1],
r: [1, 0],
},
3: {
lt: [-1, -1],
ld: [-1, 0],
r: [1, 0]
}
}

示例代码如下:

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

// 创建随机颜色组合
function createColorArr(num) {
let arr = []
for (let n = 0; n < num; n++) {
arr.push(PIXI.utils.rgb2hex([
Math.random() * 1,
Math.random() * 1,
Math.random() * 1,
]))
}
return arr
}

let game = new PIXI.Application({ width: 800, height: 600 })
document.getElementById('preview-box').appendChild(game.view)
game.view.style.maxWidth = "100%"

/**
* 创建基本素材三角形
* @param {number} 单边长度
*/
function createTriangle(len) {
// let h = Math.pow(Math.pow(w, 2) - Math.pow(w / 2, 2), .5)

let canvas = document.createElement('canvas')
canvas.width = len / 2;
canvas.height = len;

let ctx = canvas.getContext('2d')
ctx.fillStyle = '#ffffff'
ctx.lineStyle = '#ffffff'
ctx.beginPath()
ctx.moveTo(0, 0)
ctx.lineTo(len / 2, len / 2)
ctx.lineTo(0, len)
ctx.closePath()
ctx.fill()

return PIXI.Texture.from(canvas)
}

let texture = createTriangle(32);

let container = new PIXI.Container(1024)
container.position.set(game.screen.width / 2, game.screen.height / 2)
game.stage.addChild(container)
window.page = container

let colSum = Math.floor(game.screen.width / texture.width * 2) + 2 // * 因为一列要放两个
let rowSum = Math.floor(game.screen.height / texture.height) + 2 // 因为上下要溢出半列 加起来多一列

for (let col = 0; col < colSum; col++) {
for (let row = 0; row < rowSum; row++) {
// 以竖边中心点为锚点定位
// aIndex 竖边序号
let aIndex = Math.ceil(col / 2)

let x = aIndex * texture.width
let y = row * texture.height

let sp = new PIXI.Sprite(texture);
sp.position.set(x - (game.screen.width / 2), y - (game.screen.height / 2));
sp.scale.set(1.001)
sp.name = `${col}-${row}`;
sp.anchor.set(0, .5);
sp.alpha = 0
sp.interactive = true

if (col % 2 !== 0) {
sp.angle += 180
}

if (aIndex % 2 === 1) {
sp.y += texture.height / 2
}

container.addChild(sp)
}
}

let last = [26, 4];
let now = [26, 5];

let handle = {
0: {
rt: [1, -1],
rd: [1, 0],
l: [-1, 0]
},
2: {
rt: [1, 0],
rd: [1, 1],
l: [-1, 0]
},
1: {
lt: [-1, 0],
ld: [-1, 1],
r: [1, 0],
},
3: {
lt: [-1, -1],
ld: [-1, 0],
r: [1, 0]
}
}

let frame = 0
PIXI.Ticker.shared.add(()=>{
if(frame%10===0){
let direct
let cOrder = now[0] % 4;
if (cOrder === 0 || cOrder === 2) {
direct = random(['rt', 'rd', 'l'])
} else {
direct = random(['lt', 'ld', 'r'])
}
let step = handle[cOrder][direct]
let next = [
now[0] + step[0],
now[1] + step[1],
]

let ele = container.getChildByName(`${next[0]}-${next[1]}`)

// 阻止走入已填色的区域
if (ele?.alpha !== 0) return;

if (ele) {
ele.alpha = .5
ele.tint = random([
0x999999,
0xcccccc,
0xffffff
])
last = [...now]
now = [...next]
}
}
frame++
})
  1. 前言