Write at the beginning
Recently, wechat has updated 8.0. One of the most fun things is the update of expression package. Everyone has played the expression package war in the group.
As a frontend programmer, this aroused my curiosity. Although I have never implemented such animation, I still can't help but want to implement it. Finally, I spent two days looking at the source code of some libraries to achieve a similar effect. Here I summarize and teach you how to learn and implement it. and 🎉 There is a name of its own, called colorful paper scraps, and its English name is "confetti".
Chat room + colorful confetti effect online address: https://www.qiufengh.com/#/
Chat room Github address: https://github.com/hua1995116/webchat
Colorful confetti Github address: https://github.com/hua1995116/nodedemo/tree/master/confetti
For the reason of time, I only realized the color block of parallelogram, and the principle of other shapes is similar.
You can also set the direction
Preliminary study
Before writing this special effect, I hardly knew how to use canvas, although I don't know how to use it now, and many API s are not clear. Therefore, this tutorial is also written based on zero basis canvas. You don't have to worry that this tutorial is too difficult to be discouraged. I will realize it step by step on the basis of zero basis canvas. However, before learning this special effect, you need a little knowledge of high school mathematics. If you still remember sin and cos functions, the following contents will be very simple for you. It doesn't matter~
Personally, I prefer to explore and research, and I will study interesting things. Therefore, on the basis of giants, I went to codepen to check many similar implementations for research.
Finally, the target is canvas confitti. Why is this library? Because its effect is very good for us, and it is an open source library, and has a {1.3K} star (I feel I can analyze the principle of the boss's implementation library another day ~), and the maintenance frequency is also very high.
Core implementation
Slice scene
I was a little happy when I got this library first, because this library has only one single file.
But when I opened this file, I found that it was wrong One file has 500 lines of code. I peel off layers of custom configured code, and finally extract the motion track of a single piece of paper. I began to constantly observe its trajectory Infinite loop observation
You can see that it is doing a motion similar to a parabola, and then I label the variables in the source code one by one, and then combine it with the source code.
fetti.x += Math.cos(fetti.angle2D) * fetti.velocity; fetti.y += Math.sin(fetti.angle2D) * fetti.velocity + fetti.gravity;
It's ok if I don't understand the above code. I just prove the writing method in the source code and provide some ideas for learning the source code. The following is the real opening implementation!
Implementation of parallelogram
Before implementing this feature, we need to know several functions of canvas. More view( https://www.runoob.com/jsref/domobjcanvas.html )
beginPath
Method to start a path or reset the current path.
moveTo
Move the path to the specified point in the canvas without creating a line.
lineTo
Add a new point, and then create a line from that point to the last specified point in the canvas.
closePath
Creates a path from the current point back to the starting point.
fill
Fills the current drawing (path).
fillStyle
Sets or returns the color, gradient, or mode used to fill a painting.
Since we want to realize colorful paper scraps, I must realize a paper scraps first. Let's realize a parallelogram paper scraps!
We all know that the implementation of parallelogram in css is a div, which is a box by default, but it is not so convenient in canvas, so how to implement a parallelogram?
Four points, we only need to know four points to determine a parallelogram. The coordinate system in canvas is slightly different from our ordinary web pages. It takes the upper left corner as the starting point, but it does not affect.
I can draw a parallelogram with a width of 20, (0, 0), (0, 20), (20,20), (20,0).
...(Some pre initialization code is omitted) var context = canvas.getContext('2d'); // Clear canvas context.clearRect(0, 0, canvas.width, canvas.height); // Set the color and start drawing context.fillStyle = 'rgba(2, 255, 255, 1)'; context.beginPath(); // Set several points var point1 = { x: 0, y: 0 } var point2 = { x: 0, y: 20 } var point3 = { x: 20, y: 20 } var point4 = { x: 20, y: 0 } // Draw 4 points context.moveTo(Math.floor(point1.x), Math.floor(point1.y)); context.lineTo(Math.floor(point2.x), Math.floor(point2.y)); context.lineTo(Math.floor(point3.x), Math.floor(point3.y)); context.lineTo(Math.floor(point4.x), Math.floor(point4.y)); // Complete the route and fill in context.closePath(); context.fill();
To sum up, we only need one point to determine the initial position of the parallelogram (0, 0). If we know another angle (90 degrees) and the variable length of the parallelogram (20), we can determine the position of the whole parallelogram! (only junior high school knowledge is needed to locate the whole parallelogram).
Well, you've taken a big step towards success by learning to draw this! Isn't it simple~
Bosses: that's it?
Well, that's it.
path of particle
By constantly debugging the track motion of each frame of canvas confitti, it is found that it always does an xaxis variable deceleration motion (it will not continue to move until the speed is 0), and the yaxis is also a variable deceleration motion first and then an average speed motion. The following is the approximate track diagram.
This is his trajectory. Don't think it looks very difficult, but the core code is only three sentences.
// fetti.angle2D is an angle (this angle determines a value between trajectory 3 / 2 * Math.PI  2 * Math.PI. The above range is selected because the trajectory must move in the negative direction to move to the upper left corner), // fetti.velocity is a value with an initial length of 50. // fetti.gravity = 3 fetti.x += Math.cos(fetti.angle2D) * fetti.velocity; // fetti.x coordinate of the first point fetti.y += Math.sin(fetti.angle2D) * fetti.velocity + fetti.gravity; // fetti.y coordinate of the first point fetti.velocity *= 0.8；
To sum up, the x cycle of the first coordinate point is always increasing by a negative value (Math.cos(3 / 2 * Math.PI  2 * Math.PI) is always negative), and this value is decreasing. The yaxis of the first point is always added with a negative value math Cos (3 / 2 * math.pi  2 * math. PI) is always negative), but due to {fetti Gravity is always positive, so at a critical point, the value of y will continue to increase.
I simulated the following coordinates. In order to let you understand this trajectory, the following coordinate axes are opposite to those in canvas. I also processed the data in the opposite direction.
Use a square with 10 sides to realize the trajectory.
const fetti = { "x": 445, "y": 541, "angle2D": 3 / 2 * Math.PI + 1 / 6 * Math.PI, "color": {r: 20, g: 30, b: 50}, "tick": 0, "totalTicks": 200, "decay": 0.9, "gravity": 3, "velocity": 50 } var animationFrame = null; const update = () => { context.clearRect(0, 0, canvas.width, canvas.height); context.fillStyle = 'rgba(2, 255, 255, 1)'; context.beginPath(); fetti.x += Math.cos(fetti.angle2D) * fetti.velocity; // First point fetti.y += Math.sin(fetti.angle2D) * fetti.velocity + fetti.gravity; // First point var x1 = fetti.x; var y1 = fetti.y; var x2 = fetti.x;// Second point var y2 = fetti.y + 10; // Second point var x3 = x1 + 10; var y3 = y1 + 10; var x4 = fetti.x + 10; var y4 = fetti.y; fetti.velocity *= fetti.decay; context.moveTo(Math.floor(x1), Math.floor(y1)); context.lineTo(Math.floor(x2), Math.floor(y2)); context.lineTo(Math.floor(x3), Math.floor(y3)); context.lineTo(Math.floor(x4), Math.floor(y4)); context.closePath(); context.fill(); animationFrame = raf.frame(update); }
Does it smell like that except for color and shape?
Reverse effect
So how to make this fall more natural and have a feeling of falling?
In fact, he has been doing a flip effect
To disassemble them is to rotate around a point. The whole process is to turn itself over and move according to the motion track.
To realize this special effect, in fact, it was mentioned before when realizing a square to realize a square. Satisfying the following three points can realize a parallelogram.

Know the location of a point

Know an angle

Know the length of one side
At present, what I can determine is that the position of a point is easy to determine, which is our starting point. Then we know the side length, which is one angle away. As long as our angle changes constantly, we can achieve the above special effects.
const update = () => { context.clearRect(0, 0, canvas.width, canvas.height); context.fillStyle = 'rgba(2, 255, 255, 1)'; context.beginPath(); fetti.velocity *= fetti.decay; fetti.tiltAngle += 0.1 // Keep changing the angle of this quadrilateral var length = 10; var x1 = fetti.x; var y1 = fetti.y; var x2 = fetti.x + (length * Math.sin(fetti.tiltAngle));// Second point var y2 = fetti.y + (length * Math.cos(fetti.tiltAngle)); // Second point var x3 = x2 + 10; var y3 = y2; var x4 = fetti.x + length; var y4 = fetti.y; context.moveTo(Math.floor(x1), Math.floor(y1)); context.lineTo(Math.floor(x2), Math.floor(y2)); context.lineTo(Math.floor(x3), Math.floor(y3)); context.lineTo(Math.floor(x4), Math.floor(y4)); context.closePath(); context.fill(); animationFrame = raf.frame(update); }
In this way, we have realized the above special effects.
Combined motion
Then combine what we have written above to form a complete special effect.
const update = () => { context.clearRect(0, 0, canvas.width, canvas.height); context.fillStyle = 'rgba(2, 255, 255, 1)'; context.beginPath(); fetti.x += Math.cos(fetti.angle2D) * fetti.velocity; // First point fetti.y += Math.sin(fetti.angle2D) * fetti.velocity + fetti.gravity; // First point fetti.velocity *= fetti.decay; fetti.tiltAngle += 0.1 // Keep changing the angle of this quadrilateral var length = 10; var x1 = fetti.x; var y1 = fetti.y; var x2 = fetti.x + (length * Math.sin(fetti.tiltAngle));// Second point var y2 = fetti.y + (length * Math.cos(fetti.tiltAngle)); // Second point var x3 = x2 + 10; var y3 = y2; var x4 = fetti.x + length; var y4 = fetti.y; context.moveTo(Math.floor(x1), Math.floor(y1)); context.lineTo(Math.floor(x2), Math.floor(y2)); context.lineTo(Math.floor(x3), Math.floor(y3)); context.lineTo(Math.floor(x4), Math.floor(y4)); context.closePath(); context.fill(); animationFrame = raf.frame(update); }
Final form
If you want to achieve the final state, there are many small blocks, fading and random colors!
Set how many frames disappear. Here we have two variables totalTicks and tick to customize to control how many frames the small blocks disappear.
As for multiple small blocks, we only need to make a for loop.
And random colors, made a list of colors.
const colors = [ '#26ccff', '#a25afd', '#ff5e7e', '#88ff5a', '#fcff42', '#ffa62d', '#ff36ff' ]; var arr = [] for (let i = 0; i < 20; i++) { arr.push({ "x": 445, "y": 541, "velocity": (45 * 0.5) + (Math.random() * 20), "angle2D": 3 / 2 * Math.PI + Math.random() * 1 / 4 * Math.PI, "tiltAngle": Math.random() * Math.PI, "color": hexToRgb(colors[Math.floor(Math.random() * 7)]), "shape": "square", "tick": 0, "totalTicks": 200, "decay": 0.9, "random": 0, "tiltSin": 0, "tiltCos": 0, "gravity": 3, }) }
See the complete code
https://github.com/hua1995116/nodedemo/blob/master/confetti/%E5%AE%8C%E6%95%B4demo.html
Add some food
To realize the expression war of the war form of multihuman. In our wechat, the expression sending is not a single point, but a multi person form. Therefore, we can continue to explore and use the combination of websocket and colorful blocks.
Here we need to pay attention to several points. (due to space reasons, I won't explain websocket. Let's mention the key points of implementation.).
 We can use a tag to distinguish between historical messages and realtime messages
 Distinguish whether it is sent by oneself or receive other people's message to change the direction of confetti.
 Only for single 🎉 Animation will only be carried out when.
 First zoom in and out animation, delay 200ms, and then come out with special effects
if(this.msg === '🎉' && this.status) { this.confetti = true; const rect = this.$refs.msg.querySelector('.msgtext').getBoundingClientRect(); if(rect.left && rect.top) { setTimeout(() => { confetti({ particleCount: r(100, 150), angle: this.isSelf ? 120 : 60, spread: r(45, 80), origin: { x: rect.left / window.innerWidth, y: rect.top / window.innerHeight } }); }, 200) } }
More exploration
When drawing very many squares with canvas, we will compare carton. At this time, we can use web worker to calculate, so as to improve performance. Please explore it by yourself, or see the source code of canvas confitti~
last
Looking back at the author's previous highly praised articles, you may gain more!

Talking about frontend watermark from cracking a design website (detailed tutorial): 790 + praise

The frontend novice guide I learned from the glory of the king: 260 + likes

One article takes you to unlock the mystery of "file download": 140 + likes

10 cross domain solutions (with the ultimate trick): 940 + likes

One article to understand the whole process of file upload (1.8w word indepth analysis, advanced necessary): 260 + likes
epilogue
❤️ Follow + like + collect + comment + forward ❤️， Original is not easy, encourage the author to create better articles
Note the official account of autumn wind, a front end official account focused on frontend interview, engineering and open source.
 Reply your resume after paying attention and get 100 + sets of exquisite resume templates
 After paying attention, reply to your friends and pull you into the technical exchange group + interview exchange group
 Welcome to the notes of autumn wind