Using canvas to realize wizard animation

Keywords: Javascript IE html5

The article was first published on a personal blog: http://heavenru.com

In the recent project, we need to implement a sprite animation. The material side only provides a short video material. So before we implement the sprite animation, we first introduce two tools to help us better meet the requirements. In this article, two command-line tools are introduced to convert a short video file into a sprite image and how to use canvas to draw wizard animation.

The official addresses of the two tools are as follows:

1. ffmpeg Video Transfer Tool

ffmpeg is "a complete cross-platform solution for recording, converting and streaming audio and video transmission tools." Its role is not limited to what is described in this article. Interested students can go to the official website to learn more by themselves.

Converting Video to Picture Output

Basic Usage

./ffmpeg -i jellyfish.mp4 -vf scale=138:-1 -r 8 %04d.png
  • - i Video Stream Input URL

  • - vf creates filters specified by filters and uses them to filter streams, which are the descriptions of filters to be applied to streams, and must have a single input and a single output of the same type of stream. The corresponding filter parameters must follow this or they will not work.

  • Scale video scaling, scale=width:height where, if height=-1, the adaptive height, according to the video output aspect ratio, followed by the scale=width:height,setar=16:9 can specify the output aspect ratio.

  • - R video output fps value, the greater the value, the higher the fps slice video, alias-framerate, for example, we want to use-r 60 to clip the video to export pictures.

  • - Aspect video output aspect ratio, such as 4:3 and 16:9, are standard parameter usage.

  • - The start position of SS clipping is a very useful parameter, which means that the video is clipped at a certain time. The parameter is placed in front of - i, and the parameter format hh:mm:ss is time-seconds.

  • - t duration, which means the length of video that needs to be clipped, can be used with-ss to clip the content of any video time period. For example, we need to clip 5-10 seconds of video export. We can use ffmgeg-ss 00:00:05-t 00:10 in this way.

  • - V frames sets the number of output video frames, which is an alias for - frames:v

  • - qscale:v 2 specifies the quality of the output picture. The range of values is 2-31. The larger the value, the worse the quality. It is recommended that the value be 2-5.

  • Comprehensive application:

    // Intercept a picture at 60 seconds
    ffmpeg -ss 60 -i input.mp4 -qscale:v 2 -vframes 1 output.jpg
    
    // Export all pictures at 60 FPS speed
    ffmpeg -i input.mp4 -r 60 %04d.png

    2. Merge multiple pictures into one picture montage

    With the tools described above, we can easily convert a video into a series of image files, and then we can use them. montage Tool combines n previously exported pictures into one picture

    Basic usage:

    montage -border 0 -geometry 138x -tile 89x -quality 100% *.png myvideo.jpg

    3. Drawing canvas Elf Animation

    Before we start editing the code, let's sort out the requirements:

    OK, after understanding our needs, we started to write code. Let's start with a suggested parameter merging tool approach

    var _extends = Object.assign || function (target) {
      for (var i = 1; i < arguments.length; i++) {
        var source = arguments[i];
        for (var key in source) { // Traversing the attributes of the incoming object
          if (Object.prototype.hasOwnProperty.call(source, key)) { // Only the attributes and methods on the instance are manipulated to avoid circular prototypes
            target[key] = source[key];
          }
        }
      }
      return target;
    }

    Next is our canvas wizard object

    function Sprite(canvas, opts) {
      var defaults = {
        loop: false,  // Whether to Play Loop or not
        frameIndex: 0,  // What frame is the current frame?
        startFrameIndex: 0, // Actually rendering location
        tickCount: 0, // Counters in each time period
        ticksPerFrame: 1, // The number of frames per rendering period, which controls the rendering speed of the animation
        numberOfFrames: 1, // Number of total animation frames
        numberOfPerLine: undefined, // Number of frames per action
        width: 0, // Canvas width
        height: 0, // Screen Height
        sprite: undefined  // image object
      };
    
      var params = opts || {};
      this.canvas = canvas;
      this.ctx = canvas.getContext('2d');
      this.options = _extends({}, defaults, params);
    
      if (this.image) throw new Error('Please pass in the picture object');
    
      // The reason for taking Math.min() here is that under safari, if the size of the picture exceeds the size of the canvas, no image will be rendered.
      // So here we go to the little ones in the canvas and pictures.
      this.options.width = Math.min(this.canvas.width, this.options.sprite.width);
      this.options.height = Math.min(this.canvas.height, this.options.sprite.height);
      if (!this.options.numberOfPerLine) {
        this.options.numberOfPerLine = this.options.numberOfFrames || 9999;
      }
    }
    
    Sprite.prototype.render = function () {
      this.ctx.clearRect(0, 0, this.options.width, this.options.height);
      // The core drawing code mainly uses canvas.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) API.
      // This. options. frameIndex%. This. options. numberOfPerLine calculates the remainder each time to determine whether to change lines or not.
      // Math.floor(this.options.frameIndex / this.options.numberOfPerLine)
      this.ctx.drawImage(this.options.sprite, this.options.width * (this.options.frameIndex % this.options.numberOfPerLine), this.options.height * Math.floor(this.options.frameIndex / this.options.numberOfPerLine), this.options.width, this.options.height, 0, 0, this.options.width, this.options.height);
    }
    
    Sprite.prototype.update = function () {
      this.options.tickCount++;
      // The core part of frame rate control is to determine whether the current counter is larger than the value we pass in at each drawing time point.
      if (this.options.tickCount > this.options.ticksPerFrame) {
        this.options.tickCount = 0;
    
        // Animation Cycle Judgment
        if (this.options.frameIndex < this.options.numberOfFrames - 1) {
          this.options.frameIndex++;
        } else if (this.options.loop) {
          // Each loop starts with a given startFrameIndex
          this.options.frameIndex = this.options.startFrameIndex;
        }
      }
    }

    Now that our Wizard class is basically complete, let's look at how it can be used in business code.

    var spriteCanvas = document.getElementById('spriteCanvas');
    spriteCanvas.width = 138;
    spriteCanvas.height = 308;
    var isSpriteLoaded = false;
    var spriteImage = new Image();
    var sprite;
    
    // Here is a BUG under IE, if our sprite is not loaded in the image, it will be executed.
    // Then a DOM Exception will be thrown under IE
    // So we put Sprite initialization in the image.onlaod callback function to execute
    sprite.onload = function () {
      sprite = new Sprite(spriteCanvas, {
        sprite: spriteImage,
        loop: true,
        numberOfFrames: 92,
        ticksPerFrame: 3
      });
    
      spriteAnimate();
    }
    
    sprite.src = 'xxxxx/sprite.jpg';
    
    function spriteAnimate() {
      requestAnimationFrame(spriteAnimate);
      sprite.render();
      sprite.update();
    }

    The article is basically completed here. If you want to see the specific effect, you can check it here.
    Portal: Jellyfish AnimationHummingbird animation

    Reference material

    https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/drawImage
    http://www.williammalone.com/articles/create-html5-canvas-javascript-sprite-animation/

    Posted by chris.zeman on Sat, 08 Jun 2019 15:20:26 -0700