Written in the beginning

Recently, wechat updated 8.0, one of the most fun is the update of the emoji, everyone in the group have played the emoji war.

As a front-end programmers, it reminded me of my curiosity, although I’ve never been to realize this animation, but I still can’t help but want to achieve, in the end I took 2 days to see some library source code to achieve a similar effect on my own, I summarize here, and hand and teach you how to learn. And 🎉 has a name of its own. It’s called confetti.

Chat room + Confetti online address: www.qiufengh.com/#/

Chat room Github: github.com/hua1995116/…

Confetti Github: github.com/hua1995116/…

Special effects preview, due to time I only implemented the parallelogram color pieces, other shapes are similar principle.

You can also set the direction

The early stage of the study

I barely knew how to use Canvas before I wrote this effect, although I still don’t know much about it now, and I don’t know much about the API, so this tutorial is based on a zero-base canvas, so you don’t have to worry about being told off because it’s too difficult. I will implement it step by step on the basis of zero base Canvas. You’ll need a little high school math before you can learn this trick. If you remember sines and cosines, the following should be easy for you

I personally like to explore research, and I will do research on interesting things, so I also stood on the basis of giants, and went to Codepen to look up many similar implementations for research.

Finally, the target was Canvas-Confetti. Why this library? Because its effect is very good for us, and it is an open source library, and has 1.3K star (I feel I can analyze the principle of the library later), maintenance frequency is very high.

The core to realize

Slice the scene

When I first got the library, I was kind of happy because it was a single file.

However, when I opened the file, it was wrong… A file of 500 lines of code, I stripped layers of some custom configured code, and finally extracted the movement of a single scrap of paper. I’ve been watching it continuously… An infinite loop of observation…

You can see it’s doing a parabolic motion, and then I annotate the variables in the source code one by one, and then combine the source code.

fetti.x += Math.cos(fetti.angle2D) * fetti.velocity;
fetti.y += Math.sin(fetti.angle2D) * fetti.velocity + fetti.gravity;
Copy the code

The above code can not understand also fine, I just prove the source code in writing, and provide some ideas to learn source code, the following is the real start to achieve!

Implementation of the parallelogram

To implement this feature, we need to know several canvas functions. For more information (www.runoob.com/jsref/dom-o…

beginPath

Method to start a path, or reset the current path.

moveTo

Moves the path to the specified point on the canvas without creating a line.

lineTo

Add a new point and then create a line on the canvas from that point to the last specified point.

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 the painting.

Since we are going to implement confetti, I must implement a confetti first, so let’s implement a parallelogram confetti!

We all know that in CSS, a parallelogram is a div, and the default is a box. In Canvas, it is not that convenient, so how to implement a parallelogram?

Four points, we only need to know four points to identify a parallelogram. The coordinate system in canvas is slightly different from our ordinary page writing. It starts from the top left corner, but it doesn’t matter.

I can draw a parallelogram of width 20, (0, 0), (0, 20), (20,20), (20,0).

. (Omitted some preinitialization code)var context = canvas.getContext('2d');
// Clear the 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 four 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 it
context.closePath();
context.fill();
Copy the code

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 length of the parallelogram (20), we can determine the position of the entire parallelogram! (It only takes junior high school knowledge to locate the entire parallelogram.)

Well, you’ve taken a big step towards learning to draw this! Isn’t that simple

Bosses inside OS: Is that it?

Yeah, that’s it.

trajectory

By continuously debugging the trajectory of Canvas-Confetti for each frame, it is found that it is always doing a variable deceleration on the X-axis (until the speed reaches zero), and a variable deceleration and then an average speed on the Y-axis. The following is a general trajectory diagram.

This is his trajectory, and don’t think it’s hard, 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, which is a negative direction for the trajectory 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 The x coordinate of the first point
fetti.y += Math.sin(fetti.angle2D) * fetti.velocity + fetti.gravity; // fetti. Y The y coordinate of the first point
fetti.velocity *= 0.8;Copy the code

In summary, the x-week value of the first coordinate is always increasing by a negative value (Math.cos(3/2 * Math.pi-2 * Math.pi) is always negative), and the value is decreasing. Cos (3/2 * math.pi – 2 * Math.pi) is always negative), but because fetti. Gravity is always positive, at some point the value of y increases.

I simulated the following coordinates, because in order for you to understand the trajectory, the following axes are opposite to the canvas, and I also processed the data in the opposite direction.

Use a square with a 10 on the side to implement 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; // The first point
  fetti.y += Math.sin(fetti.angle2D) * fetti.velocity + fetti.gravity; // The first point

  var x1 = fetti.x;
  var y1 = fetti.y;

  var x2 = fetti.x;// The second point
  var y2 = fetti.y + 10; // The 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);
}
Copy the code

Does it smell like anything other than color and shape?

Inversion of the special effects

So how do you make this fall more natural, a feeling of falling?

In fact, he was doing a flip effect all the time.

To take them apart is to do a rotational motion around a point, and the whole process is to flip itself and move along a trajectory.

Implementing this effect, actually, I mentioned earlier when implementing a square, implementing a square. A parallelogram can be achieved by satisfying the following three points.

  • You know where a point is

  • Know an Angle

  • I know the length of one side

As far as I can tell, the position of a point is easy to determine, which is our starting point, and then the length of our side is known, and we need an Angle, so as long as our Angle is constantly changing, we can achieve this effect.

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 the quadrilateral

  var length = 10;

  var x1 = fetti.x;
  var y1 = fetti.y;

  var x2 = fetti.x + (length * Math.sin(fetti.tiltAngle));// The second point
  var y2 = fetti.y + (length * Math.cos(fetti.tiltAngle)); // The 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);
}

Copy the code

This is how we achieve the above effects.

Combination of movement

And then put together what we wrote above to make a complete 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; // The first point
  fetti.y += Math.sin(fetti.angle2D) * fetti.velocity + fetti.gravity; // The first point

  fetti.velocity *= fetti.decay;
  fetti.tiltAngle += 0.1 // Keep changing the Angle of the quadrilateral

  var length = 10;

  var x1 = fetti.x;
  var y1 = fetti.y;

  var x2 = fetti.x + (length * Math.sin(fetti.tiltAngle));// The second point
  var y2 = fetti.y + (length * Math.cos(fetti.tiltAngle)); // The 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);
}
Copy the code

Final shape

If you want to achieve the final state, all you need is multiple chunks, fade away, and random colors!

Set how many frames to disappear. We have two variables, totalTicks and tick, to control how many frames to disappear.

For multiple pieces, we just have to do a for loop.

And random colors, I made a colors list.

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})},Copy the code

See the complete code

Github.com/hua1995116/…

Add some meal

Achieve emoji wars in multiplayer battle mode. In our wechat emoji is not a single point, but a multi-player form, so we can continue to explore, using WebSocket and the combination of colorful pieces.

There are a couple of things to note here. (For space reasons, I won’t go into WebSocket, but I will mention some implementation points.)

  • We can go through one tagTo distinguish between historical and real-time messages
  • Distinguish between your own message and someone else’s to redirect the confetti.
  • Animation is only performed for a single 🎉.
  • First zoom in and out of the animation, delay 200ms before the special effects
if(this.msg === '🎉' && this.status) {
        this.confetti = true;
        const rect = this.$refs.msg.querySelector('.msg-text').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)}}Copy the code

To explore more

In this case, we can use the Web worker to calculate, so as to improve the performance. This is for readers to explore. You can also see the source code of Canvas – Confetti

The last

Look back at my previous highly praised articles, maybe you can harvest more oh!

  • From cracking a design website about front-end watermarking (detailed tutorial) : 790+ thumbs up

  • Front end novice guide I learned from King of Glory: 260+ likes

  • This article unlocks the secret of “file download” : 140+ likes

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

  • This article to understand the whole process of file upload (1.8w word depth analysis, advanced prerequisite) : 260+ points like

conclusion

❤️ follow + like + collect + comment + forward ❤️, original is not easy, encourage the author to create better articles

Follow public AccountAutumn wind notes, a focus on the front-end interview, engineering, open source front-end public number

  • Follow and replyresumeGet 100+ sets of excellent resume templates
  • Follow and replyGood friendI invite you to the technical exchange group + interview exchange group, and you can also discuss the practice content with me.
  • Welcome to attentionAutumn wind notes