Two weeks ago, the project needed to achieve a “like” effect with fluttering hearts. Scratching his head for a long time, saw a few big guy’s article, and finally touched a 788. Can not help but sigh, or vegetables. Let’s look at the effect :(The portal goes in and clicks a wave)

First, Bezier curve trajectory

To describe demand in plain English is to ask a picture of a heart to follow a Bezier curve and disappear as it goes. The idea is to get a series of points on the Bezier curve. This article will not explain the principle of Bezier curves, because the masters have already done it, and better than I have. References are as follows:

  • Draw a curve animation on canvas – in-depth understanding of Bezier curves
  • Beziermaker.js – N order Bezier curve generator

In the second article, it is explained that the second and third order Bezier curves can be generated using canvas’s own methods: quadraticCurveTo and bezierCurveTo, while the higher order bezier curves can be generated by obtaining a series of points on the curve and then connecting these points in sequence to fit the higher order Bezier curves. That’s right. We want this set of points, and with these points, we can control the path of the heart. The following is a demo I wrote based on the author’s Beziermarket.js, which can intuitively see the points on the higher-order Bessel curve:

XyWZXj

The coordinates of the points on the above 100 curves are calculated from the following code:

BezierMaker.prototype.bezier = function(t) { // Call bezier formula
    var x = 0,
        y = 0,
        bezierCtrlNodesArr = this.bezierCtrlNodesArr,
        n = bezierCtrlNodesArr.length - 1,
        self = this
    bezierCtrlNodesArr.forEach(function(item, index) {
        if(! index) { x += item.x *Math.pow(( 1 - t ), n - index) * Math.pow(t, index) 
            y += item.y * Math.pow(( 1 - t ), n - index) * Math.pow(t, index) 
        } else {
            x += self.factorial(n) / self.factorial(index) / self.factorial(n - index) * item.x * Math.pow(( 1 - t ), n - index) * Math.pow(t, index) 
            y += self.factorial(n) / self.factorial(index) / self.factorial(n - index) * item.y * Math.pow(( 1 - t ), n - index) * Math.pow(t, index) 
        }
    })
    return {
        x: x,
        y: y
    }
}
Copy the code

This method is an implementation of Bessel’s formula. Take the third-order Bessel formula (see figure below), whose equation requires four control points (P1,P2,P3,P4) and a t-value to calculate the coordinates of a point on the curve.

According to the giventValue, combined with the coordinates of control points, calculate the correspondingtThe coordinates of a point on the Bezier curve under phi. Taking the graph (from the first article), given a t value of 0.25, the coordinates of point B can be obtained

When t increases from 0 to 1, 100 points on the curve can be obtained, and then the corresponding curve can be fitted. By the time we get to this set of points, we’ve solved more than half of the problem.

Two, make the heart float

After getting the set of fitting points, to draw the track is to take the coordinates from the array and draw the picture of the heart onto the corresponding coordinates. Change the opacity of the image according to the position of the current fitting point in the curve array.

// Generate a random number
function rnd () {
  let flag = Math.random() > 0.5 ? 1 : -1
  return 80 * Math.random() * flag
}

class FlyHeart {
  constructor (ctx, img) {
    this.ctx = ctx;
    this.img = heart;
    // Get the movement of the heart, a series of fitting points coordinates
    this.bezierArr = new BezierMaker(ctx, [
      {x: 187.y: 245},
      {x: 170 + rnd(), y: 200},
      {x: 200 + rnd() , y: 120}, 
      {x: 140 + rnd(), y: 60}].90).bezierArr //90 indicates the number of fitting points. RND makes the trajectory of the heart somewhat random
  }
  draw () {
  	// Retrieve each point of the track in turn
    let position = this.bezierArr.shift();
    
    // Clear the last drawing
    this.clear();
    
    if (position) {
      this.ctx.save()
      // Calculate the transparency based on the current array length
      this.ctx.globalAlpha = this.bezierArr.length / 30;
      this.ctx.drawImage(this.img, position.x , position.y, 20.20);
      this.ctx.restore();
      this.prevPosition = position; }}// Clear the last drawing
  clear () {
    if (this.prevPosition) {
      this.ctx.clearRect(this.prevPosition.x, this.prevPosition.y, 20.20); }}}Copy the code

The next step is to add a click event to the body. When clicked, a new red heart is generated:

  document.body.addEventListener('click'.function() {
    heartArr.push(new FlyHeart(ctx, heart));
  })
  
  let heartArr = []
  const cvs = document.getElementById('cvs')
  const ctx = cvs.getContext('2d')
  const heart = document.getElementById('heart') / / picture
  
  function draw () {
    if(heartArr.length) {
      for(let heart of heartArr) {
        heart.draw();
        if(heart.bezierArr.length === 0) {
          heart.clear();
          let index = heartArr.indexOf(heart)
          heartArr.splice(index, 1)
        }
      }
    }
    requestAnimationFrame(draw)
  }
  draw()
Copy the code

Three, afterword.

At that time, WHEN I saw this demand, I was really at a loss. When I saw the N-order Bezier curve, I was even at a loss. However, IF I didn’t understand it, I still had to read it. I hope I didn’t waste your time. I hope you have learned something after watching it.