使用 canvas 實現精靈動畫

文章首發於我的博客:http://heavenru.comjavascript

在最近項目中須要實現一個精靈動畫,素材方只提供了一個短視頻素材,因此在實現精靈動畫以前先介紹兩個工具來幫助咱們更好的實現需求。在這篇文章中,主要是介紹兩個命令行工具來實現將一個短視頻文件轉化成一張 sprite 圖片與如何使用 canvas 繪製精靈動畫php

兩個工具官方地址以下:html

一、ffmpeg 視頻轉圖片工具

ffmpeg 是「一個完整的跨平臺解決方案,用於記錄,轉換和流式傳輸音頻和視頻的工具」,它的做用原不止於這篇文章中所介紹的,有興趣的同窗能夠本身去官方網站了解更多。canvas

將視頻轉成圖片輸出

基本用法

./ffmpeg -i jellyfish.mp4 -vf scale=138:-1 -r 8 %04d.png
  • -i 視頻流輸入 URLbash

  • -vf 建立由過濾器指定的過濾器,並使用它過濾流,過濾器是要應用於流的過濾器的描述,而且必須具備相同類型流的單個輸入和單個輸出。對應的過濾器參數必須跟在這個以後,否則沒法生效ide

  • scale 視頻縮放,scale=width:height 其中,若是 height=-1 ,則表示自適應高度,按照視頻的寬高比輸出,後面緊接這 scale=width:height,setar=16:9 則能夠指定輸出寬高比函數

  • -r 視頻輸出 fps 值, 值越大,則以越高的 fps 切片視頻,別名 -framerate,好比咱們想以 60fps 去裁剪視頻導出圖片,則使用 -r 60工具

  • -aspect 視頻輸出寬高比,好比經常使用的 4:316:9 都是規範的參數用法

  • -ss 裁剪開始位置,表示從視頻的某個時間開始裁剪,是一個很是有用的參數,該參數使用位置放在 -i 前面,參數格式 hh:mm:ss 表示時分秒

  • -t 持續時間,表示須要裁剪的視頻長度,一般配合 -ss 一塊兒使用,就能實現裁剪任意視頻時間段的內容了,好比咱們須要裁剪 5-10 秒的視頻導出,能夠這麼配合使用 ffmgeg -ss 00:00:05 -t 00:00:10

  • -vframes 設定輸出視頻幀數,它是 -frames:v 的別名

  • -qscale:v 2 指定輸出圖片質量,取值範圍2-31,值越大,質量越差,建議取值 2-5

綜合應用:

// 截取 60 秒處的一張圖片
ffmpeg -ss 60 -i input.mp4 -qscale:v 2 -vframes 1 output.jpg

// 將視頻按照 60fps 的速度導出全部圖片
ffmpeg -i input.mp4 -r 60 %04d.png

二、合併多個圖片爲一張圖片 montage

經過上面介紹的工具,咱們能很輕易的將一個視頻轉化爲一系列的圖片文件,那麼這個時候,咱們就可使用 montage 工具將前面導出的 n 張圖片合併爲一張圖片

基本用法:

montage -border 0 -geometry 138x -tile 89x -quality 100% *.png myvideo.jpg
  • -tile 表明須要合併的一行圖片數量,當超出這個數字的時候,將換行合併

  • -quality 表明合成圖片質量,取值範圍 0 - 100%

三、繪製 canvas 精靈動畫

在開始編輯代碼以前,咱們整理一下需求:

  • 動畫須要能循環播放

  • 動畫須要能指定從某一幀開始渲染

  • 指定渲染多少幀動畫

  • 動畫須要能控制渲染幀率

  • 當精靈圖片不是單行的時候,要能實現自動換行渲染

OK,明白了咱們的需求以後,咱們開始編寫代碼。先來一個簡易的參數合併工具方法

var _extends = Object.assign || function (target) {
  for (var i = 1; i < arguments.length; i++) {
    var source = arguments[i];
    for (var key in source) { // 遍歷傳入的對象的屬性
      if (Object.prototype.hasOwnProperty.call(source, key)) { // 只操做該實例上的屬性和方法, 避免循環原型
        target[key] = source[key];
      }
    }
  }
  return target;
}

接下來是咱們的 canvas 精靈對象

function Sprite(canvas, opts) {
  var defaults = {
    loop: false,  // 是否循環播放
    frameIndex: 0,  // 當前第幾幀
    startFrameIndex: 0, // 其實渲染位置
    tickCount: 0, // 每一個時間段內計數器
    ticksPerFrame: 1, // 每一個渲染時間段幀數,經過這個來控制動畫的渲染速度
    numberOfFrames: 1, // 動畫總幀數
    numberOfPerLine: undefined, // 每行動畫幀數
    width: 0, // 畫布寬度
    height: 0, // 畫屏高度
    sprite: undefined  // 圖片 image 對象
  };

  var params = opts || {};
  this.canvas = canvas;
  this.ctx = canvas.getContext('2d');
  this.options = _extends({}, defaults, params);

  if (this.image) throw new Error('請傳入圖片對象');

  // 這裏的取 Math.min() 的緣由是,在 safari 下面,若是圖片的大小超過了畫布的大小,那麼將不會渲染任何圖像
  // 因此在這裏,咱們去畫布和圖片中的小者。
  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);
  // 核心繪製代碼,主要使用了 canvas.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) API
  // this.options.frameIndex % this.options.numberOfPerLine 每次求餘數,判斷是否換行
  // 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++;
  // 控制幀率的核心部分,在每一個繪製時間點,判斷當前的計數器是否大於咱們傳入的值
  if (this.options.tickCount > this.options.ticksPerFrame) {
    this.options.tickCount = 0;

    // 動畫循環判斷
    if (this.options.frameIndex < this.options.numberOfFrames - 1) {
      this.options.frameIndex++;
    } else if (this.options.loop) {
      // 每次循環都從給定的 startFrameIndex 開始
      this.options.frameIndex = this.options.startFrameIndex;
    }
  }
}

到這裏,咱們的精靈類基本完成了,接下來看下具體在業務代碼中如何使用它

var spriteCanvas = document.getElementById('spriteCanvas');
spriteCanvas.width = 138;
spriteCanvas.height = 308;
var isSpriteLoaded = false;
var spriteImage = new Image();
var sprite;

// 這裏有個 IE 下的 BUG,若是咱們的 sprite 在圖片沒有加載徹底就執行
// 那麼在 IE 下面會拋出一個 DOM Exception
// 所以咱們將 Sprite 初始化放在了 image.onlaod 回調函數中執行
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();
}

文章到這裏基本完成了,想要看具體效果的同窗,能夠去這裏查看
傳送門: 水母動畫蜂鳥動畫

參考資料

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

相關文章
相關標籤/搜索