0%

canvas画布实现烟花绽放效果

效果图

前言

项目有个场景需求是要实现点击时,出现烟花绽放的效果,在网上百度了很多关于烟花的效果图,免费的不好看,好看的要钱,贫穷的打工人选择自己上手。

实现步骤

1、分解

将所有烟花都分解成一个类似水滴形状的瓣,控制它的大小、坐标和旋转角度,叠加在一起就成了一朵好看的烟花。一个瓣可以看做是两条弯曲的曲线构成,曲线可由三次贝塞尔曲线绘制。

2、水平绘制

假设初始坐标值为(x,y),瓣的宽度为w,高度为h,第一个拐角点设在宽度0.9位置(经过我的多次测试,这个位置绘制出的烟花最好看!)。

1
2
3
4
ctx.beginPath();
ctx.moveTo(x,y); // 先将点放到初始坐标值
ctx.bezierCurveTo(x+w*0.9,y-h/2,x+w, y-h/2, x+w,y);
ctx.bezierCurveTo(x+w,y+h/2,x+w*0.9,y+h/2,x,y);// 第二条曲线以第一条曲线的结束点为起始点
3、旋转角度

假设旋转角度为45度,如图计算出每个坐标的坐标值。

4、花瓣合成

一层烟花由八朵烟花花瓣组成,分为左、右、上、下、左上、右上、左下、右下。由于canvas画布的坐标只有正数没有负数,因此可以计算出:

// 左 及 左上
目标横坐标 = 初始位置横坐标 - 对应点横坐标
目标纵坐标 = 初始位置纵坐标 - 对应点点纵坐标
// 上 及 右上
目标横坐标 = 初始位置横坐标 + 对应点点横坐标
目标纵坐标 = 初始位置纵坐标 - 对应点点纵坐标
// 右 及 右下
目标横坐标 = 初始位置横坐标 + 对应点点横坐标
目标纵坐标 = 初始位置纵坐标 + 对应点点纵坐标
// 下 及 左下
目标横坐标 = 初始位置横坐标 - 对应点点横坐标
目标纵坐标 = 初始位置纵坐标 + 对应点点纵坐标

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
// 假设a为偏移的角度,(x,y)为起始点坐标值,h为花瓣高度,w为花瓣宽度
// 左边花瓣代码示例
var angel = a;//偏移的角度
var sinAngel = Math.sin(angel*Math.PI / 180);
var cosAngel = Math.cos(angel*Math.PI / 180);
var tanAngel = Math.tan(angel*Math.PI / 180);
var hypotenuse = h / 2 / Math.sin(angel*Math.PI / 180);
var p1x = x - ((w * rate - h / 2 / tanAngel) * cosAngel + hypotenuse);
var p1y = y - (w * rate - h / 2 / tanAngel) * sinAngel;
var p2x = x - ((w - h / 2 / tanAngel) * cosAngel + hypotenuse);
var p2y = y - (w - h / 2 / tanAngel) * sinAngel ;
var p3x = x - w * cosAngel;
var p3y = y - w * sinAngel;
var p4x = x - ((w + h / 2 / tanAngel) * cosAngel - hypotenuse);
var p4y = y - (w + h / 2 / tanAngel) * sinAngel;
var p5x = x - ((w * rate + h / 2 / tanAngel) * cosAngel - hypotenuse);
var p5y = y - (w * rate + h / 2 / tanAngel) * sinAngel;
ctx.beginPath();
ctx.moveTo(x,y);
ctx.stroke();
ctx.bezierCurveTo(p1x,p1y,p2x, p2y,p3x,p3y);
ctx.bezierCurveTo(p4x, p4y,p5x,p5y,x,y);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
5、圆点坐标值

烟花可看做一圈一圈不同半径上的点为起始坐标的花瓣组成,每一圈有八个花瓣,根据三角函数可计算出点的坐标。

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
// 假设偏移角度为angle,圆心坐标为(x,y),半径为r
// 左
var p1x = x - r * Math.cos(angle * Math.PI / 180);
var p1y = y - r * Math.sin(angle * Math.PI / 180);
// 右
var p2x = x + r * Math.cos(angle * Math.PI / 180);
var p2y = y + r * Math.sin(angle * Math.PI / 180);
// 上
var p3x = x + r * Math.sin(angle * Math.PI / 180);
var p3y = y - r * Math.cos(angle * Math.PI / 180);
// 下
var p4x = x - r * Math.sin(angle * Math.PI / 180);
var p4y = y + r * Math.cos(angle * Math.PI / 180);
// 左上
var p5x = x - r * Math.cos((angle + 45) * Math.PI / 180);
var p5y = y - r * Math.sin((angle + 45) * Math.PI / 180);
// 右上
var p6x = x + r * Math.cos((45 - angle) * Math.PI / 180);
var p6y = y - r * Math.sin((45 - angle) * Math.PI / 180);
// 左下
var p7x = x - r * Math.cos((45 - angle) * Math.PI / 180);
var p7y = y + r * Math.sin((45 - angle) * Math.PI / 180);
// 右下
var p8x = x + r * Math.cos((angle + 45) * Math.PI / 180);
var p8y = y + r * Math.sin((angle + 45) * Math.PI / 180);
6、最终实现
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
// 圆心横坐标、    圆心纵坐标、   半径、   偏移角度、     瓣高  、     瓣宽、    颜色 
// 第一圈
calcEight(pointR, pointR, canvasW * 0.05, 1,canvasW * 0.02, canvasW * 0.15, "#E4AF6A");
calcEight(pointR, pointR, canvasW * 0.05, 11,canvasW * 0.02, canvasW * 0.15, "#F8EEB0");
// 第二圈
calcEight(pointR, pointR, canvasW * 0.1, 28,canvasW * 0.08, canvasW * 0.35, "#E4AF6A");
calcEight(pointR, pointR, canvasW * 0.1, 36,canvasW * 0.08, canvasW * 0.35, "#F8EEB0");
// 第三圈
calcEight(pointR, pointR, canvasW * 0.075, 20,canvasW * 0.058, canvasW * 0.3, "#E4AF6A");
calcEight(pointR, pointR, canvasW * 0.075, 28,canvasW * 0.058, canvasW * 0.3, "#F8EEB0");
// 第四圈
calcEight(pointR, pointR, canvasW * 0.29, 9,canvasW * 0.03, canvasW * 0.2, "#E4AF6A");
calcEight(pointR, pointR, canvasW * 0.29, 1,canvasW * 0.03, canvasW * 0.2, "#F8EEB0");
// 左
function draw1(x,y,h,w, r,a,color)
{
var angel = a;
var sinAngel = Math.sin(angel*Math.PI / 180);
var cosAngel = Math.cos(angel*Math.PI / 180);
var tanAngel = Math.tan(angel*Math.PI / 180);
var hypotenuse = h / 2 / Math.sin(angel*Math.PI / 180);
var p1x = x - ((w * rate - h / 2 / tanAngel) * cosAngel + hypotenuse);
var p1y = y - (w * rate - h / 2 / tanAngel) * sinAngel;
var p2x = x - ((w - h / 2 / tanAngel) * cosAngel + hypotenuse);
var p2y = y - (w - h / 2 / tanAngel) * sinAngel ;
var p3x = x - w * cosAngel;
var p3y = y - w * sinAngel;
var p4x = x - ((w + h / 2 / tanAngel) * cosAngel - hypotenuse);
var p4y = y - (w + h / 2 / tanAngel) * sinAngel;
var p5x = x - ((w * rate + h / 2 / tanAngel) * cosAngel - hypotenuse);
var p5y = y - (w * rate + h / 2 / tanAngel) * sinAngel;
ctx.beginPath();
ctx.moveTo(x,y);
ctx.stroke();
ctx.bezierCurveTo(p1x,p1y,p2x, p2y,p3x,p3y);
ctx.bezierCurveTo(p4x, p4y,p5x,p5y,x,y);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
}
// 右
function draw2(x,y,h,w, r,a,color)
{
var angel = a;
var sinAngel = Math.sin(angel*Math.PI / 180);
var cosAngel = Math.cos(angel*Math.PI / 180);
var tanAngel = Math.tan(angel*Math.PI / 180);
var hypotenuse = h / 2 / Math.sin(angel*Math.PI / 180);
var p1x = x + ((w * rate - h / 2 / tanAngel) * cosAngel + hypotenuse);
var p1y = y + (w * rate - h / 2 / tanAngel) * sinAngel;
var p2x = x + ((w - h / 2 / tanAngel) * cosAngel + hypotenuse);
var p2y = y + (w - h / 2 / tanAngel) * sinAngel ;
var p3x = x + w * cosAngel;
var p3y = y + w * sinAngel;
var p4x = x + ((w + h / 2 / tanAngel) * cosAngel - hypotenuse);
var p4y = y + (w + h / 2 / tanAngel) * sinAngel;
var p5x = x + ((w * rate + h / 2 / tanAngel) * cosAngel - hypotenuse);
var p5y = y + (w * rate + h / 2 / tanAngel) * sinAngel;
ctx.beginPath();
ctx.moveTo(x,y);
ctx.bezierCurveTo(p1x,p1y,p2x, p2y,p3x,p3y);
ctx.bezierCurveTo(p4x, p4y,p5x,p5y,x,y);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
}
// 上
function draw3(x,y,h,w, r,a,color)
{
var angel = 90 - a;
var sinAngel = Math.sin(angel*Math.PI / 180);
var cosAngel = Math.cos(angel*Math.PI / 180);
var tanAngel = Math.tan(angel*Math.PI / 180);
var hypotenuse = h / 2 / Math.sin(angel*Math.PI / 180);
var p1x = x + ((w * rate - h / 2 / tanAngel) * cosAngel + hypotenuse);
var p1y = y - (w * rate - h / 2 / tanAngel) * sinAngel;
var p2x = x + ((w - h / 2 / tanAngel) * cosAngel + hypotenuse);
var p2y = y - (w - h / 2 / tanAngel) * sinAngel ;
var p3x = x + w * cosAngel;
var p3y = y - w * sinAngel;
var p4x = x + ((w + h / 2 / tanAngel) * cosAngel - hypotenuse);
var p4y = y - (w + h / 2 / tanAngel) * sinAngel;
var p5x = x + ((w * rate + h / 2 / tanAngel) * cosAngel - hypotenuse);
var p5y = y - (w * rate + h / 2 / tanAngel) * sinAngel;
ctx.beginPath();
ctx.moveTo(x,y);
ctx.bezierCurveTo(p1x,p1y,p2x, p2y,p3x,p3y);
ctx.bezierCurveTo(p4x, p4y,p5x,p5y,x,y);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
}
// 下
function draw4(x,y,h,w, r,a,color)
{
var angel = 90 - a;
var sinAngel = Math.sin(angel*Math.PI / 180);
var cosAngel = Math.cos(angel*Math.PI / 180);
var tanAngel = Math.tan(angel*Math.PI / 180);
var hypotenuse = h / 2 / Math.sin(angel*Math.PI / 180);
var p1x = x - ((w * rate - h / 2 / tanAngel) * cosAngel + hypotenuse);
var p1y = y + (w * rate - h / 2 / tanAngel) * sinAngel;
var p2x = x - ((w - h / 2 / tanAngel) * cosAngel + hypotenuse);
var p2y = y + (w - h / 2 / tanAngel) * sinAngel ;
var p3x = x - w * cosAngel;
var p3y = y + w * sinAngel;
var p4x = x - ((w + h / 2 / tanAngel) * cosAngel - hypotenuse);
var p4y = y + (w + h / 2 / tanAngel) * sinAngel;
var p5x = x - ((w * rate + h / 2 / tanAngel) * cosAngel - hypotenuse);
var p5y = y + (w * rate + h / 2 / tanAngel) * sinAngel;
ctx.beginPath();
ctx.moveTo(x,y);
ctx.bezierCurveTo(p1x,p1y,p2x, p2y,p3x,p3y);
ctx.bezierCurveTo(p4x, p4y,p5x,p5y,x,y);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
}
// 左上
function draw5(x,y,h,w, r,a,color)
{
var angel = a + 45;
var sinAngel = Math.sin(angel*Math.PI / 180);
var cosAngel = Math.cos(angel*Math.PI / 180);
var tanAngel = Math.tan(angel*Math.PI / 180);
var hypotenuse = h / 2 / Math.sin(angel*Math.PI / 180);
var p1x = x - ((w * rate - h / 2 / tanAngel) * cosAngel + hypotenuse);
var p1y = y - (w * rate - h / 2 / tanAngel) * sinAngel;
var p2x = x - ((w - h / 2 / tanAngel) * cosAngel + hypotenuse);
var p2y = y - (w - h / 2 / tanAngel) * sinAngel ;
var p3x = x - w * cosAngel;
var p3y = y - w * sinAngel;
var p4x = x - ((w + h / 2 / tanAngel) * cosAngel - hypotenuse);
var p4y = y - (w + h / 2 / tanAngel) * sinAngel;
var p5x = x - ((w * rate + h / 2 / tanAngel) * cosAngel - hypotenuse);
var p5y = y - (w * rate + h / 2 / tanAngel) * sinAngel;
ctx.beginPath();
ctx.moveTo(x,y);
ctx.stroke();
ctx.bezierCurveTo(p1x,p1y,p2x, p2y,p3x,p3y);
ctx.bezierCurveTo(p4x, p4y,p5x,p5y,x,y);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
}
// 右上
function draw6(x,y,h,w, r,a,color)
{
var angel = 45 - a;
var sinAngel = Math.sin(angel*Math.PI / 180);
var cosAngel = Math.cos(angel*Math.PI / 180);
var tanAngel = Math.tan(angel*Math.PI / 180);
var hypotenuse = h / 2 / Math.sin(angel*Math.PI / 180);
var p1x = x + ((w * rate - h / 2 / tanAngel) * cosAngel + hypotenuse);
var p1y = y - (w * rate - h / 2 / tanAngel) * sinAngel;
var p2x = x + ((w - h / 2 / tanAngel) * cosAngel + hypotenuse);
var p2y = y - (w - h / 2 / tanAngel) * sinAngel ;
var p3x = x + w * cosAngel;
var p3y = y - w * sinAngel;
var p4x = x + ((w + h / 2 / tanAngel) * cosAngel - hypotenuse);
var p4y = y - (w + h / 2 / tanAngel) * sinAngel;
var p5x = x + ((w * rate + h / 2 / tanAngel) * cosAngel - hypotenuse);
var p5y = y - (w * rate + h / 2 / tanAngel) * sinAngel;
ctx.beginPath();
ctx.moveTo(x,y);
ctx.bezierCurveTo(p1x,p1y,p2x, p2y,p3x,p3y);
ctx.bezierCurveTo(p4x, p4y,p5x,p5y,x,y);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
}
// 左下
function draw7(x,y,h,w, r,a,color)
{
var angel = 45 - a;
var sinAngel = Math.sin(angel*Math.PI / 180);
var cosAngel = Math.cos(angel*Math.PI / 180);
var tanAngel = Math.tan(angel*Math.PI / 180);
var hypotenuse = h / 2 / Math.sin(angel*Math.PI / 180);
var p1x = x - ((w * rate - h / 2 / tanAngel) * cosAngel + hypotenuse);
var p1y = y + (w * rate - h / 2 / tanAngel) * sinAngel;
var p2x = x - ((w - h / 2 / tanAngel) * cosAngel + hypotenuse);
var p2y = y + (w - h / 2 / tanAngel) * sinAngel ;
var p3x = x - w * cosAngel;
var p3y = y + w * sinAngel;
var p4x = x - ((w + h / 2 / tanAngel) * cosAngel - hypotenuse);
var p4y = y + (w + h / 2 / tanAngel) * sinAngel;
var p5x = x - ((w * rate + h / 2 / tanAngel) * cosAngel - hypotenuse);
var p5y = y + (w * rate + h / 2 / tanAngel) * sinAngel;
ctx.beginPath();
ctx.moveTo(x,y);
ctx.bezierCurveTo(p1x,p1y,p2x, p2y,p3x,p3y);
ctx.bezierCurveTo(p4x, p4y,p5x,p5y,x,y);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
}
// 右下
function draw8(x,y,h,w, r,a,color)
{
var angel = 45 + a;
var sinAngel = Math.sin(angel*Math.PI / 180);
var cosAngel = Math.cos(angel*Math.PI / 180);
var tanAngel = Math.tan(angel*Math.PI / 180);
var hypotenuse = h / 2 / Math.sin(angel*Math.PI / 180);
var p1x = x + ((w * rate - h / 2 / tanAngel) * cosAngel + hypotenuse);
var p1y = y + (w * rate - h / 2 / tanAngel) * sinAngel;
var p2x = x + ((w - h / 2 / tanAngel) * cosAngel + hypotenuse);
var p2y = y + (w - h / 2 / tanAngel) * sinAngel ;
var p3x = x + w * cosAngel;
var p3y = y + w * sinAngel;
var p4x = x + ((w + h / 2 / tanAngel) * cosAngel - hypotenuse);
var p4y = y + (w + h / 2 / tanAngel) * sinAngel;
var p5x = x + ((w * rate + h / 2 / tanAngel) * cosAngel - hypotenuse);
var p5y = y + (w * rate + h / 2 / tanAngel) * sinAngel;
ctx.beginPath();
ctx.moveTo(x,y);
ctx.bezierCurveTo(p1x,p1y,p2x, p2y,p3x,p3y);
ctx.bezierCurveTo(p4x, p4y,p5x,p5y,x,y);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
}
function calcEight(x,y,r, angle,h, w, color)
{
// 左
var p1x = x - r * Math.cos(angle * Math.PI / 180);
var p1y = y - r * Math.sin(angle * Math.PI / 180);
// 右
var p2x = x + r * Math.cos(angle * Math.PI / 180);
var p2y = y + r * Math.sin(angle * Math.PI / 180);
// 上
var p3x = x + r * Math.sin(angle * Math.PI / 180);
var p3y = y - r * Math.cos(angle * Math.PI / 180);
// 下
var p4x = x - r * Math.sin(angle * Math.PI / 180);
var p4y = y + r * Math.cos(angle * Math.PI / 180);
// 左上
var p5x = x - r * Math.cos((angle + 45) * Math.PI / 180);
var p5y = y - r * Math.sin((angle + 45) * Math.PI / 180);
// 右上
var p6x = x + r * Math.cos((45 - angle) * Math.PI / 180);
var p6y = y - r * Math.sin((45 - angle) * Math.PI / 180);
// 左下
var p7x = x - r * Math.cos((45 - angle) * Math.PI / 180);
var p7y = y + r * Math.sin((45 - angle) * Math.PI / 180);
// 右下
var p8x = x + r * Math.cos((angle + 45) * Math.PI / 180);
var p8y = y + r * Math.sin((angle + 45) * Math.PI / 180);
// 左
draw1(p1x, p1y, h, w, Math.PI,angle,color);
// 右
draw2(p2x, p2y, h, w, Math.PI,angle,color);
// 上
draw3(p3x, p3y, h, w, Math.PI / 2,angle,color);
// 下
draw4(p4x, p4y, h, w, Math.PI / 2,angle,color);
// 左上
draw5(p5x, p5y, h, w, Math.PI,angle,color);
// 右上
draw6(p6x, p6y, h, w, Math.PI,angle,color);
// 左下
draw7(p7x, p7y, h, w, Math.PI / 2,angle,color);
// 右下
draw8(p8x, p8y, h, w, Math.PI / 2,angle,color);
}
7、动画效果

将画布的大小分成30,每50ms加一份,以此来改变每个花瓣的大小以及初始坐标的位置,达到逐渐变大的动画效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var pointR = canvasWidth / 2;
var cw = 0;
var aT = 0;
var cwSpeed = canvasWidth / 30;// 画布大小增长速度
var aSpeed = 45 / 30;
var timer = setInterval(function()
{
if(cw < canvasWidth)
{
cw += cwSpeed;
ctx.clearRect(0, 0, canvasWidth, canvasWidth);
ctx.fill();
draw(cw, pointR, aT);
}
else
{
ctx.globalAlpha = 0;
clearInterval(timer);
}
}, 50);
-------------The End-------------
坚持原创技术分享,您的支持将鼓励我继续创作!