This article has participated in the third “topic writing” track of the Denver Creators Training Camp. For details, check out: Digg Project | Creators Training Camp third is ongoing, “write” to make a personal impact.

Animation is motion, and motion is an object changing its state (position, shape, size, etc.) in space over time.

Animation frame, a series of discrete images played at extremely fast speed in succession, so as to simulate object movement or change. At a frame rate of 24 frames per second, your brain perceives it as motion. And the human eye cannot recognize frames at higher frequencies.

Almost all projection motion media use frames for animation.

Program frame, a frame in a program is a description of an image (not the image of a moment in time in the animation frame). Successive frames follow certain rules to build subsequent frames. Program frames can reduce volume, but complex program frames may have high performance requirements.

The basic steps of animation

  1. Empty canvas: Before drawing each frame of animation, empty everything. The easiest way to clear it isclearRect().
  2. Save canvas state: If the state of canvas will be changed during drawing (color, moving the origin of coordinates, etc.) and the original state is kept during drawing every frame, it is better to save the state of canvas.
  3. Draw an animated graphic: Draws an animated graphic of the current frame.
  4. Restore canvas state: If the canvas state has been saved before, it will be restored after the current frame is drawn.

In order to perform animation, you need to perform redraw methods periodically, which can be implemented using setInterval(), setTimeout(), and requestAnimationFrame() (requestAnimationFrame is recommended).

The following code is implemented on the following 800*500 canvas:

<canvas id="c1" width="800" height="500" style="border: 1px solid #000;"></canvas>
Copy the code

setInterval()Timed execution function

SetInterval () is a timer that is used to execute a piece of code on a regular basis. With this specific function, the canvas redraw can be called on a regular basis to achieve animation effects.

SetInterval (function, milliseconds) timed execution is not strictly fixed interval. Because milliseconds will include how long it took the function to execute.

The setTimeout() function is recommended for the effect of executing a piece of code at a fixed interval first.

As follows, create a rectangle and redraw the rectangle through setInterval customization. During drawing, the X-axis coordinates are constantly changing and the motion is uniform.

let canvas = document.getElementById('c1');
let ctx = canvas.getContext('2d');
/ / rectangle
let rectProps = {
   x: 0.y: 0.width: 100.height: 100.fillStyle: 'red'.strokeStyle:"blue"
};

let StartX = rectProps.x, EndX = canvas.width - rectProps.width;
let NowX = StartX;
let Speed = 10;

/ / move
let translateRect=function(){
   NowX += Speed;

   if (NowX >= EndX) {
       NowX = EndX;                
   }
   // move the x-coordinate
   rectProps.x = NowX;

   // Clear the canvas with a Rect indicating the size of the cleared area
   ctx.clearRect(0.0, canvas.width, canvas.height);
   // Draw a rectangle
   drawRect(rectProps);
   return NowX === EndX;
}
/ / to draw
function drawRect(r) {
   ctx.beginPath();
   ctx.rect(r.x, r.y, r.width, r.height);
   ctx.fillStyle = r.fillStyle;
   ctx.fill();

   ctx.strokeStyle = r.strokeStyle;
   ctx.lineWidth = 2;
   ctx.stroke();
}

// Execute translateRect on time
let timer = setInterval(function () {
   if (translateRect()) {
       clearInterval(timer); }},1000 / 24); // 24 times per second
Copy the code

By periodically executing the setInterval above, we get a moving red rectangle:

setTimeout()Delay the

SetTimeout () is used to delay the execution of a function for a specified time. Nested calls to setTimeout() can be used to execute a piece of code at a fixed interval.

The specific usage is similar to setInterval

requestAnimationFrame

RequestAnimationFrame () is usually recommended for animations because setTimeout and setInterval may not run at the specified time and may not be executed at the correct page redraw, affecting the browser’s drawing performance, etc.

The callback functions specified by setTimeout and setInterval will not start executing until all synchronization tasks of the current event loop have been completed.

Since it is uncertain how long the previous tasks will take to complete, there is no guarantee that the tasks specified by setTimeout and setInterval will be executed at the scheduled time.

RequestAnimationFrame is an HTML5 API that specifically requests animation frames. It is up to the system to determine the execution of the callback function (depending on various factors such as the display refresh rate) without causing frame loss and stuttering.

requestAnimationFrameTo loop, it needs to be called again in a callbackrequestAnimationFrame

Tell the browser window. RequestAnimationFrame () – you want to perform an animation, and required the browser until the next redraw calls the specified callback function to update the animation.

This method takes as an argument a callback function that is executed before the browser’s next redraw.

Use requestAnimationFrame to animate the rectangle with uniform displacement above:

// Replace the setInterval section above. Everything else stays the same.
window.requestAnimationFrame(function anima(){
    if(! translateRect()) {window.requestAnimationFrame(anima); }})/ * / / or function loop () {window. RequestAnimationFrame (() = > {the if (! translateRect()) { loop(); }})}) (); * /
Copy the code

The effect is similar to what we saw before.

The basic motion of an object

Rectangles and small balls

Create a new rectangle class and small ball class, complete the rectangle and ball drawing, the following animation drawing are directly using the method of these two classes.

  • rect.js
/* Rectangle class */
class Rect{
    constructor(props){
        this.x = 0;
        this.y = 0;
        this.width = 100;
        this.height=70;

        // Stroke and fill colors
        this.strokeStyle = "Rgba (0,0,0,0)";// Transparent black, no stroke
        this.fillStyle = "red"
        this.alpha = 1;

        Object.assign(this, props);// Initialize the props configuration parameters
        return this;
    }
    render(ctx) {
        let { x, y, r, width, height, fillStyle, strokeStyle, alpha } = this;
        ctx.save();
        // Move the origin of coordinates to draw coordinates
        ctx.translate(x, y);
        ctx.strokeStyle = strokeStyle,
        ctx.fillStyle = fillStyle;
        ctx.globalAlpha = alpha;
        ctx.beginPath();
        ctx.rect(0.0, width, height);
        ctx.fill();
        ctx.stroke();

        ctx.restore();
        return this; }}Copy the code
  • ball.js
/* Small ball */
class Ball{
    constructor(props){
        // ball coordinates default 0,0 radius 20
        this.x=0;
        this.y=0;
        this.r=20;
        // Horizontal and vertical scaling multiple
        this.scaleX=1;
        this.scaleY=1;

        // Stroke and fill colors
        this.strokeStyle="Rgba (0,0,0,0)";// Transparent black, no stroke
        this.fillStyle="red" 
        this.alpha=1;

        Object.assign(this,props);// Initialize the props configuration parameters
        return this;
    }
    render(ctx){
        let {x,y,r,scaleX,scaleY,fillStyle,strokeStyle,alpha}=this;
        ctx.save();
        // Move the origin of coordinates to draw coordinates
        ctx.translate(x,y);
        ctx.scale(scaleX,scaleY);
        ctx.strokeStyle=strokeStyle,
        ctx.fillStyle=fillStyle;
        ctx.globalAlpha=alpha;
        ctx.beginPath();
        ctx.arc(0.0,r,0.2*Math.PI);
        ctx.fill();
        ctx.stroke();

        ctx.restore();
        return this; }}Copy the code

Constant Motion — Animation process (6 steps)

Uniform motion can be regarded as a process in which an object changes uniformly from the value of one state to the value of another state.

Therefore, the processing process of uniform motion animation is as follows (a total of six steps) :

  1. Set the initial and end values. Find the change delta= final – initial.
let start = 10, end = 100;
let delta=end-start;
Copy the code

You can assume that the beginning and the end are changes in x position.

  1. Set the total step to complete the action step, and the current number of steps nowStep
let nowStep = 0, step = 80;
Copy the code
  1. Change nowStep, where k is a constant. Constant speed is to ensure that each change in the current number of steps is the same.
nowStep+=k;
Copy the code
  1. Evaluate the current state value and draw

    The current value is the initial value + total changes * the ratio of current steps to total steps (i.e., the initial value + current completed steps).

    The current number of steps is 0, the current value is equal to the start value; the current number of steps is equal to the total number of steps, the execution is complete, the current value is equal to the start value + change value.

now=start+ delta*(nowStep/step)
Copy the code
  1. Draw with the current value.

  2. Loop the process from step 3 until the current number of steps >= total

These six steps are basically the overall processing process of animation drawing. Different motion forms (change forms) are only different in the change and processing of state values.

Achieve a constant speed animation

The following is a uniform change of the position and size of a rectangle from one state to another:

let rectProps = {
            x: 0.y: 0.width: 100.height: 100.fillStyle: 'red'.strokeStyle:"blue"
        };

//1, set the initial value and the end value to find the change delta= final value - initial value
let startX = rectProps.x, endX = 300;
let startY = rectProps.y, endY = 400;
let startW = rectProps.width, endW = rectProps.width + 200;
//2, set the total step size step and the current number of steps
let nowStep = 0, step = 280;
//3. Constant speed ensures that each change in the current number of steps is the same
/ / change nowStep
//4, the current state value, and draw
// The current value is the initial value plus the total change * the ratio of the current steps to the total steps
// The current number of steps is 0, the current value is equal to the start value, the current number of steps is equal to the total number of steps, complete, the current value is equal to the start value + change amount.
// now = start + delta * (nowStep / step)

//5. Draw with the current value
//6. Loop the process from 3 until the current number of steps >= total number of steps

let rect=new Rect(rectProps).render(ctx);

(function loop() {
  window.requestAnimationFrame(function () {
      if (nowStep >= step) {
          nowStep = step;
      }

      nowStep += 1;
      // Change properties
      rect.x = startX + (endX - startX) * (nowStep / step);
      rect.y = startY + (endY - startY) * (nowStep / step);
      rect.width = startW + (endW - startW) * (nowStep / step);

      ctx.clearRect(0.0, canvas.width, canvas.height);

      / / redraw
      rect.render(ctx);

      if (nowStep < step) {                
          loop();
      }
  });
})();
Copy the code

Variable motion

The principle of uniform speed motion

Uniform speed can be divided into uniform acceleration and deceleration. The core changes in the current number of nowStep at each change, the change is constant increase or decrease.

The overall processing process is the same as that of the above uniform motion, and the amount of change in the third nowStep change is also changed.

The processing principle of the specific change value is as follows:

// Acceleration dv=dv+a a is constant (positive uniform acceleration; Negative uniform deceleration; 0 is constant speed. V is speed, faster or slower every time.
// An initial positive value of acceleration can also be specified.
dv += a;

// The current average velocity v=v+ dV /2 is the average of the previous velocity + the current acceleration
v += dv/2;

// Current steps = last steps + current speed (change speed)
nowStep += v;

// The constant 2 is removed.
dv += a;
nowStep += dv;
Copy the code

Achieve uniform acceleration animation

let startX = rectProps.x, endX = 300;
let startY = rectProps.y, endY = 400;
let startW = rectProps.width, endW = rectProps.width + 200;
let nowStep = 0, step = 80;

// Set the initial acceleration to 0
let dv =0; 
let a=0.1; // The acceleration increases by 0.1 each time

let rect=new Rect(rectProps).render(ctx);

(function loop() {
  window.requestAnimationFrame(function () {
      if (nowStep >= step) {
          nowStep = step;
      }

      // Change properties
      rect.x = startX + (endX - startX) * (nowStep / step);
      rect.y = startY + (endY - startY) * (nowStep / step);
      rect.width = startW + (endW - startW) * (nowStep / step);
   
      ctx.clearRect(0.0, canvas.width, canvas.height);   
      / / redraw
      rect.render(ctx);

      if (nowStep < step) {
          / / acceleration
          dv += a;
          nowStep += dv;
       
          loop();
      }
  });
})();
Copy the code

Uniformly retarded

Given an initial acceleration and a negative acceleration variable (the step size can be adjusted appropriately, otherwise the effect is not easy to see). Notice what happens when the speed is zero.

As follows:

let startX = rectProps.x, endX = 300;
let startY = rectProps.y, endY = 400;
let startW = rectProps.width, endW = rectProps.width + 200;
let nowStep = 0, step = 280;

let dv =20; 
let a=-0.7; / / deceleration

let rect=new Rect(rectProps).render(ctx);

(function loop() {
  window.requestAnimationFrame(function () {
      if (nowStep >= step) {
          nowStep = step;
      }

      ctx.clearRect(0.0, canvas.width, canvas.height);
      // Change properties
      rect.x = startX + (endX - startX) * (nowStep / step);
      rect.y = startY + (endY - startY) * (nowStep / step);
      rect.width = startW + (endW - startW) * (nowStep / step);
      
      / / redraw
      rect.render(ctx);

      if (nowStep < step && dv>0) {                
          // Constant speed
          //nowStep += 1;

          / / acceleration
          dv += a;
          nowStep += dv;
       
          loop();
      }
  });
})();
Copy the code

Simulated acceleration of gravity (bouncing ball)

The acceleration of gravity is a special kind of acceleration downward, and the value of acceleration is fixed. Speed loss is caused by air friction, etc.

As follows, the ball can be bounced by simulating the acceleration of gravity:

window.onload=() = >{
   let canvas = document.getElementById("c1");
   let ctx = canvas.getContext("2d");

   let H=canvas.height;
   let W=canvas.width;
   let ball=new Ball({
       x:W/2.y:H/5
   }).render(ctx);

   let speedY=0,dy=0.5;
   let m= -0.8;// Speed loss coefficient

   (function move(){ 

       // To prevent invalid bounce speeds less than acceleration *m, we do not need to draw general math. abs(speedY)
       if (ball.y === (H - ball.r)&&Math.abs(speedY)<= Math.abs(dy * m)) {                    
       }
       else{
           speedY += dy;
           ball.y += speedY;

           if (ball.y + ball.r >= H) {
               speedY *= m;// Speed direction, m friction loss
               ball.y = H - ball.r;
           }
           ctx.clearRect(0.0, W, H);

           ball.render(ctx);

           window.requestAnimationFrame(move);
       }
   })();
}
Copy the code

Lots of random bouncing balls

At the same time, the following effect of a large number of random bouncing balls can be realized, and a certain number of balls can be randomly generated, with random positions and random colors:

let canvas = document.getElementById("c1");
let ctx = canvas.getContext("2d");

let H = canvas.height;
let W = canvas.width;
let ballTotal= 150;
let balls = [];
for (let i = 0; i < ballTotal; i++) {
    balls.push(new Ball({
        x: W * Math.random(),
        y: 50 * Math.random(),
        fillStyle: `rgb(The ${Math.floor(256 * Math.random())}.The ${Math.floor(256 * Math.random())}.The ${Math.floor(256 * Math.random())}) `.r: 8.speedY: 0}}))let  dy = 0.5;
let m = -0.9;// Speed loss coefficient

let renderCount=0;
function drawBalls(){
    // Step by step
    if(balls.length! == renderCount) { balls[renderCount].render(ctx); renderCount +=1;
    }
    ctx.clearRect(0.0, W, H);
    for (let i = 0; i < renderCount; i++) {
        // Prevent invalid bounce
        if (balls[i].y === (H - balls[i].r) && Math.abs(balls[i].speedY) <= Math.abs(dy * m)) {
        }
        else {
            balls[i].speedY += dy;
            balls[i].y += balls[i].speedY;
            if (balls[i].y + balls[i].r >= H) {
                balls[i].speedY *= m;// Speed direction, m friction lossballs[i].y = H - balls[i].r; } } balls[i].render(ctx); }} (function move() {
    window.requestAnimationFrame(move); drawBalls(); }) ();Copy the code

Variable motion

Other types of variable speed motion – non-uniform motion, can be set according to the need to change the change in speed (such as the value of the change dy random).

Here is just one — progressive deceleration.

Progressive deceleration

You can change the remaining variation by 1/ K each time (1/4 as shown below) until the target is reached:

Then, the calculation of step 3 in the animation processing process becomes:

nowStep += (step-nowStep)*k
Copy the code

As follows, by changing 1/10 of the remaining transformation each time, the ball speed and color change are realized:

 let circle = {
    x: 100.y: 100.r: 50.fillStyle: 'rgba(255, 0, 0, 1)'
};

let startX = circle.x, endX = 750;
let startAlpha = 1, endAlpha = 0.4;
let nowStep = 0, step = 600;
let k=1/10;

let ball=new Ball(circle);

(function loop() {
    window.requestAnimationFrame(function () {
        // console.log(nowStep, step)
        if (nowStep >= step) {
            nowStep = step;
            // console.log(123);
        }

        / / to erase
        ctx.clearRect(0.0, canvas.width, canvas.height);

        ball.x = startX + (endX - startX) * (nowStep / step);
        ball.fillStyle = 'rgba(255, 0, 0, ' + (startAlpha + (endAlpha - startAlpha) * (nowStep / step)) + ') ';        

        ball.render(ctx);
        if (nowStep < step) {
            // Because the remaining 1/n increment each time, never reach the target quantity, as follows, set to round up
            nowStep = nowStep + Math.ceil((step - nowStep) * k); loop(); }})}) ();Copy the code

From this point of view, increasing speed and irregular change can be set to achieve variable speed motion.