运用 canvas 完成精灵动画

文章首发于个人博客:http://heavenru.com

在近来项目中须要完成一个精灵动画,素材方只提供了一个短视频素材,所以在完成精灵动画之前先引见两个东西来协助我们更好的完成需求。在这篇文章中,主假如引见两个命令行东西来完成将一个短视频文件转化成一张 sprite 图片与怎样运用 canvas 绘制精灵动画

两个东西官方地点以下:

1、ffmpeg 视频转图片东西

ffmpeg 是「一个完整的跨平台解决方案,用于纪录,转换和流式传输音频和视频的东西」,它的作用原不止于这篇文章中所引见的,有兴致的同砚能够本身去官方网站相识更多。

将视频转成图片输出

基础用法

./ffmpeg -i jellyfish.mp4 -vf scale=138:-1 -r 8 %04d.png
  • -i 视频流输入 URL

  • -vf 建立由过滤器指定的过滤器,并运用它过滤流,过滤器是要运用于流的过滤器的形貌,而且必需具有雷同范例流的单个输入和单个输出。对应的过滤器参数必需跟在这个以后,不然没法见效

  • 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

2、兼并多个图片为一张图片 montage

经由过程上面引见的东西,我们能很随意马虎的将一个视频转化为一系列的图片文件,那末这个时刻,我们就可以够运用 montage 东西将前面导出的 n 张图片兼并为一张图片

基础用法:

montage -border 0 -geometry 138x -tile 89x -quality 100% *.png myvideo.jpg
  • -tile 代表须要兼并的一行图片数目,当超越这个数字的时刻,将换行兼并

  • -quality 代表合成图片质量,取值局限 0 - 100%

3、绘制 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/

    原文作者:离尘不理人
    原文地址: https://segmentfault.com/a/1190000010435543
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞