使用 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/

相关文章
相关标签/搜索