javascript帧动画(实例讲解)

前端之家收集整理的这篇文章主要介绍了javascript帧动画(实例讲解)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

前面的话

帧动画就是在“连续的关键帧”中分解动画动作,也就是在时间轴的每帧上逐帧绘制不同的内容,使其连续播放而成的动画。由于是一帧一帧的画,所以帧动画具有非常大的灵活性,几乎可以表现任何想表现的内容。本文将详细介绍javascript帧动画

概述

分类

常见的帧动画的方式有三种,包括gif、CSS3 animation和javascript

git和CSS3 animation不能灵活地控制动画的暂停和播放、不能对帧动画做更加灵活地扩展。另外,gif图不能捕捉动画完成的事件。所以,一般地,使用javascript来实现帧动画

【原理】

js实现帧动画有两种实现方式

1、如果有多张帧动画图片,可以用一个image标签去承载图片,定时改变image的src属性(不推荐)

2、把所有的动画关键帧都绘制在一张图片里,把图片作为元素的background-image,定时改变元素的background-position属性(推荐)

因为第一种方式需要使用多个HTTP请求,所以一般地推荐使用第二种方式

【实例】

下面是使用帧动画制作的一个实例

通用帧动画

下面来设计一个通用的帧动画库

【需求分析】

  1、支持图片预加载

  2、支持两种动画播放方式,及自定义每帧动画

  3、支持单组动画控制循环次数(可支持无限次)

  4、支持一组动画完成,进行下一组动画

  5、支持每个动画完成后有等待时间

  6、支持动画暂停和继续播放

  7、支持动画完成后执行回调函数

【编程接口】

1、loadImage(imglist)//预加载图片

2、changePosition(ele,imageUrl)//通过改变元素的background-position实现动画

3、changeSrc(ele,imglist)//通过改变image元素的src

4、enterFrame(callback)//每一帧动画执行的函数,相当于用户可以自定义每一帧动画的callback

5、repeat(times)//动画重复执行的次数,times为空时表示无限次

6、repeatForever()//无限重复上一次动画,相当于repeat()

7、wait(time)//每个动画执行完成后等待的时间

8、then(callback)//动画执行完成后的回调函数

9、start(interval)//动画开始执行,interval表示动画执行的间隔

10、pause()//动画暂停

11、restart()//动画从上一交暂停处重新执行

12、dispose()//释放资源

调用方式】

支持链式调用,用动词的方式描述接口

代码设计】

1、把图片预加载 -> 动画执行 -> 动画结束等一系列操作看成一条任务链。任务链包括同步执行和异步定时执行两种任务

2、记录当前任务链的索引

3、每个任务执行完毕后,通过调用next方法,执行下一个任务,同时更新任务链索引值

【接口定义】

/* 添加一个同步任务,去预加载图片

  • @param imglist 图片数组
    */
    FrameAnimation.prototype.loadImage = function(imglist){}

/* 添加一个异步定时任务,通过定时改变图片背景位置,实现帧动画

  • @param ele dom对象
  • @param positions 背景位置数组
  • @param imageUrl 图片URL地址
    */
    FrameAnimation.prototype.changePosition = function(ele,imageUrl){}

/* 添加一个异步定时任务,通过定时改变image标签的src属性,实现帧动画

  • @param ele dom对象
  • @param imglist 图片数组
    */
    FrameAnimation.prototype.changeSrc = function(ele,imglist){}

/* 添加一个异步定时任务,自定义动画每帧执行的任务函数

  • @param tastFn 自定义每帧执行的任务函数
    */
    FrameAnimation.prototype.enterFrame = function(taskFn){}

/* 添加一个同步任务,在上一个任务完成后执行回调函数

  • @param callback 回调函数
    */
    FrameAnimation.prototype.then = function(callback){}

/* 开始执行任务,异步定时任务执行的间隔

  • @param interval
    */
    FrameAnimation.prototype.start = function(interval){}

/* 添加一个同步任务,回退到上一个任务,实现重复上一个任务的效果,可以定义重复的次数

  • @param times 重复次数
    */
    FrameAnimation.prototype.repeat = function(times){}

/* 添加一个同步任务,相当于repeat(),无限循环上一次任务

  • */
    FrameAnimation.prototype.repeatForever = function(){}

/* 设置当前任务执行结束后到下一个任务开始前的等待时间

  • @param time 等待时长
    */
    FrameAnimation.prototype.wait = function(time){}

/* 暂停当前异步定时任务

  • */
    FrameAnimation.prototype.pause = function(){}

/* 重新执行上一次暂停的异步定时任务

  • */
    FrameAnimation.prototype.restart = function(){}

/* 释放资源

  • */
    FrameAnimation.prototype.dispose = function(){}

图片预加载

图片预加载是一个相对独立的功能,可以将其封装为一个模块imageloader.js

  • 预加载图片函数
  • @param images 加载图片的数组或者对象
  • @param callback 全部图片加载完毕后调用的回调函数
  • @param timeout 加载超时的时长
    */
    function loadImage(images,callback,timeout){
    //加载完成图片的计数器
    var count = 0;
    //全部图片加载成功的标志位
    var success = true;
    //超时timer的id
    var timeoutId = 0;
    //是否加载超时的标志位
    var isTimeout = false;
    //对图片数组(或对象)进行遍历
    for(var key in images){
    //过滤prototype上的属性
    if(!images.hasOwnProperty(key)){
    continue;
    }
    //获得每个图片元素
    //期望格式是object:{src:xxx}
    var item = images[key];
    if(typeof item === 'string'){
    item = images[key] = {
    src:item
    };
    }
    //如果格式不满足期望,则丢弃此条数据,进行下一次遍历
    if(!item || !item.src){
    continue;
    }
    //计数+1
    count++;
    //设置图片元素的id
    item.id = 'img' + key + getId();
    //设置图片元素的img,它是一个Image对象
    item.img = window[item.id] = new Image();
    doLoad(item);
    }
    //遍历完成如果计数为0,则直接调用callback
    if(!count){
    callback(success);
    }else if(timeout){
    timeoutId = setTimeout(onTimeout,timeout);
    }
  • /**

    • 真正进行图片加载的函数
    • @param item 图片元素对象
      */
      function doLoad(item){
      item.status = 'loading';
      var img = item.img;
      //定义图片加载成功的回调函数
      img.onload = function(){
      success = success && true;
      item.status = 'loaded';
      done();
      }
      //定义图片加载失败的回调函数
      img.onerror = function(){
      success = false;
      item.status = 'error';
      done();
      }
      //发起一个http(s)请求
      img.src = item.src;
      /**
    • 每张图片加载完成的回调函数
      */
      function done(){
      img.onload = img.onerror = null;
      try{
      delete window[item.id];
      }catch(e){

    }
    //每张图片加载完成,计数器减1,当所有图片加载完成,且没有超时的情况,清除超时计时器,且执行回调函数
    if(!--count && !isTimeout){
    clearTimeout(timeoutId);
    callback(success);
    }
    }
    }
    /**

    • 超时函数
      */
      function onTimeout(){
      isTimeout = true;
      callback(false);
      }
      }
      var id = 0;
      function getId(){
      return ++
      id;
      }
      module.exports = loadImage;

    时间轴

    在动画处理中,是通过迭代使用setTimeout()实现的,但是这个间隔时间并不准确。下面,来实现一个时间轴类timeline.js

    var DEFAULT_INTERVAL = 1000/60;

    //初始化状态
    var STATE_INITIAL = 0;
    //开始状态
    var STATE_START = 1;
    //停止状态
    var STATE_STOP = 2;

    var requestAnimationFrame = (function(){
    return window.requestAnimationFrame || window.webkitRequestAnimationFrame|| window.mozRequestAnimationFrame || window.oRequestAnimationFrame || function(callback){
    return window.setTimeout(callback,(callback.interval || DEFAULT_INTERVAL));
    }
    })();

    var cancelAnimationFrame = (function(){
    return window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.oCancelAnimationFrame || function(id){
    return window.clearTimeout(id);
    }
    })();
    /**

    • 时间轴类
    • @constructor
      */
      function Timeline(){
      this.animationHandler = 0;
      this.state = STATE_INITIAL;
      }
      /**
    • 时间轴上每一次回调执行的函数
    • @param time 从动画开始到当前执行的时间
      */
      Timeline.prototype.onenterframe = function(time){

    }
    /**

    • 动画开始
    • @param interval 每一次回调的间隔时间
      */
      Timeline.prototype.start = function(interval){
      if(this.state === STATE_START){
      return;
      }
      this.state = STATE_START;
      this.interval = interval || DEFAULT_INTERVAL;
      startTimeline(this,+new Date());
      }

    /**

    • 动画停止
      */
      Timeline.prototype.stop = function(){
      if(this.state !== STATE_START){
      return;
      }
      this.state = STATE_STOP;
      //如果动画开始过,则记录动画从开始到现在所经历的时间
      if(this.startTime){
      this.dur = +new Date() - this.startTime;
      }
      cancelAnimationFrame(this.animationHandler);
      }

    /**

    • 重新开始动画
      */
      Timeline.prototype.restart = function(){
      if(this.state === STATE_START){
      return;
      }
      if(!this.dur || !this.interval){
      return;
      }
      this.state = STATE_START;
      //无缝连接动画
      startTimeline(this,+new Date()-this.dur);
      }

    /**

    • 时间轴动画启动函数
    • @param timeline 时间轴的实例
    • @param startTime 动画开始时间戳
      */
      function startTimeline(timeline,startTime){
      //记录上一次回调的时间戳
      var lastTick = +new Date();
      timeline.startTime = startTime;
      nextTick.interval = timeline.interval;
      nextTick();
      /**
    • 每一帧执行的函数
      */
      function nextTick(){
      var now = +new Date();
      timeline.animationHandler = requestAnimationFrame(nextTick);
      //如果当前时间与上一次回调的时间戳大于设置的时间间隔,表示这一次可以执行回调函数
      if(now - lastTick >= timeline.interval){
      timeline.onenterframe(now - startTime);
      lastTick = now;
      }
      }
      }
      module.exports = Timeline;

    动画类实现

    下面是动画类animation.js实现的完整代码

    var loadImage = require('./imageloader');
    var Timeline = require('./timeline');
    //初始化状态
    var STATE_INITIAL = 0;
    //开始状态
    var STATE_START = 1;
    //停止状态
    var STATE_STOP = 2;
    //同步任务
    var TASK_SYNC = 0;
    //异步任务
    var TASK_ASYNC = 1;

    /**

    • 简单的函数封装,执行callback
    • @param callback 执行函数
      /
      function next(callback){
      callback && callback();
      }
      /
      帧动画库类
    • @constructor
      */
      function FrameAnimation(){
      this.taskQueue = [];
      this.index = 0;
      this.timeline = new Timeline();
      this.state = STATE_INITIAL;
      }

    /* 添加一个同步任务,去预加载图片

    • @param imglist 图片数组
      */
      FrameAnimation.prototype.loadImage = function(imglist){
      var taskFn = function(next){
      loadImage(imglist.slice(),next);
      };
      var type = TASK_SYNC;
      return this._add(taskFn,type);
      }

    /* 添加一个异步定时任务,通过定时改变图片背景位置,实现帧动画

    • @param ele dom对象
    • @param positions 背景位置数组
    • @param imageUrl 图片URL地址
      */
      FrameAnimation.prototype.changePosition = function(ele,imageUrl){
      var len = positions.length;
      var taskFn;
      var type;
      if(len){
      var me = this;
      taskFn = function(next,time){
      if(imageUrl){
      ele.style.backgroundImage = 'url(' + imageUrl + ')';
      }
      //获得当前背景图片位置索引
      var index = Math.min(time/me.interval|0,len);
      var position = positions[index-1].split(' ');
      //改变dom对象的背景图片位置
      ele.style.backgroundPosition = position[0] + 'px ' + position[1] + 'px';
      if(index === len){
      next();
      }
      }
      type = TASK_ASYNC;
      }else{
      taskFn = next;
      type = TASK_SYNC;
      }
      return this._add(taskFn,type);
      }

    /* 添加一个异步定时任务,通过定时改变image标签的src属性,实现帧动画

    • @param ele dom对象
    • @param imglist 图片数组
      */
      FrameAnimation.prototype.changeSrc = function(ele,imglist){
      var len = imglist.length;
      var taskFn;
      var type;
      if(len){
      var me = this;
      taskFn = function(next,time){
      //获得当前背景图片位置索引
      var index = Math.min(time/me.interval|0,len);
      //改变image对象的背景图片位置
      ele.src = imglist[index-1];
      if(index === len){
      next();
      }
      }
      type = TASK_ASYNC;
      }else{
      taskFn = next;
      type = TASK_SYNC;
      }
      return this._add(taskFn,type);
      }

    /* 添加一个异步定时任务,自定义动画每帧执行的任务函数

    • @param tastFn 自定义每帧执行的任务函数
      */
      FrameAnimation.prototype.enterFrame = function(taskFn){
      return this._add(taskFn,TASK_ASYNC);
      }

    /* 添加一个同步任务,在上一个任务完成后执行回调函数

    • @param callback 回调函数
      */
      FrameAnimation.prototype.then = function(callback){
      var taskFn = function(next){
      callback(this);
      next();
      };
      var type = TASK_SYNC;
      return this._add(taskFn,type);
      }

    /* 开始执行任务,异步定义任务执行的间隔

    • @param interval
      */
      FrameAnimation.prototype.start = function(interval){
      if(this.state === STATE_START){
      return this;
      }
      //如果任务链中没有任务,则返回
      if(!this.taskQueue.length){
      return this;
      }
      this.state = STATE_START;
      this.interval = interval;
      this._runTask();
      return this;

    }

    /* 添加一个同步任务,回退到上一个任务,实现重复上一个任务的效果,可以定义重复的次数

    • @param times 重复次数
      */
      FrameAnimation.prototype.repeat = function(times){
      var me = this;
      var taskFn = function(){
      if(typeof times === 'undefined'){
      //无限回退到上一个任务
      me.index--;
      me._runTask();
      return;
      }
      if(times){
      times--;
      //回退
      me.index--;
      me._runTask();
      }else{
      //达到重复次数,跳转到下一个任务
      var task = me.taskQueue[me.index];
      me._next(task);
      }
      }
      var type = TASK_SYNC;
      return this._add(taskFn,type);
      }

    /* 添加一个同步任务,相当于repeat(),无限循环上一次任务

    • */
      FrameAnimation.prototype.repeatForever = function(){
      return this.repeat();
      }

    /* 设置当前任务执行结束后到下一个任务开始前的等待时间

    • @param time 等待时长
      */
      FrameAnimation.prototype.wait = function(time){
      if(this.taskQueue && this.taskQueue.length > 0){
      this.taskQueue[this.taskQueue.length - 1].wait = time;
      }
      return this;
      }

    /* 暂停当前异步定时任务

    • */
      FrameAnimation.prototype.pause = function(){
      if(this.state === STATE_START){
      this.state = STATE_STOP;
      this.timeline.stop();
      return this;
      }
      return this;
      }

    /* 重新执行上一次暂停的异步定时任务

    • */
      FrameAnimation.prototype.restart = function(){
      if(this.state === STATE_STOP){
      this.state = STATE_START;
      this.timeline.restart();
      return this;
      }
      return this;
      }

    /* 释放资源

    • */
      FrameAnimation.prototype.dispose = function(){
      if(this.state !== STATE_INITIAL){
      this.state = STATE_INITIAL;
      this.taskQueue = null;
      this.timeline.stop();
      this.timeline = null;
      return this;
      }
      return this;
      }

    /**

    • 添加一个任务到任务队列
    • @param taskFn 任务方法
    • @param type 任务类型
    • @private
      */
      FrameAnimation.prototype._add = function(taskFn,type){
      this.taskQueue.push({
      taskFn:taskFn,type:type
      });
      return this;
      }

    /**

    • 执行任务
    • @private
      */
      FrameAnimation.prototype._runTask = function(){
      if(!this.taskQueue || this.state !== STATE_START){
      return;
      }
      //任务执行完毕
      if(this.index === this.taskQueue.length){
      this.dispose();
      return;
      }
      //获得任务链上的当前任务
      var task = this.taskQueue[this.index];
      if(task.type === TASK_SYNC){
      this._syncTask(task);
      }else{
      this._asyncTask(task);
      }
      }

    /**

    • 同步任务
    • @param task 执行的任务对象
    • @private
      */
      FrameAnimation.prototype._syncTask = function(task){
      var me = this;
      var next = function(){
      //切换到下一个任务
      me._next(task);
      }
      var taskFn = task.taskFn;
      taskFn(next);
      }

    /**

    • 异步任务
    • @param task 执行的任务对象
    • @private
      */
      FrameAnimation.prototype._asyncTask = function(task){
      var me = this;
      //定义每一帧执行的回调函数
      var enterframe = function(time){
      var taskFn = task.taskFn;
      var next = function(){
      //停止当前任务
      me.timeline.stop();
      //执行下一个任务
      me._next(task);
      };
      taskFn(next,time);
      }
      this.timeline.onenterframe = enterframe;
      this.timeline.start(this.interval);
      }

    /**

    • 切换到下一个任务,支持如果当前任务需要等待,则延时执行
    • @private
      */
      FrameAnimation.prototype._next = function(task){
      this.index++;
      var me = this;
      task.wait ? setTimeout(function(){
      me._runTask();
      },task.wait) : this._runTask();
      }

    module.exports = function(){
      return new FrameAnimation();
    }

    webpack配置

    由于animation帧动画库的制作中应用了AMD模块规范,但由于浏览器层面不支持,需要使用webpack进行模块化管理,将animation.js、imageloader.js和timeline.js打包为一个文件

    下面是一个代码实例,通过创建的帧动画库实现博客开始的动画效果

    <Meta charset="UTF-8"> Document

    更多实例

    除了可以实现兔子推车的效果,还可以使用帧动画实现兔子胜利和兔子失败的效果

    <Meta charset="UTF-8"> Document

    以上这篇javascript帧动画(实例讲解)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持编程之家。

    原文链接:https://www.f2er.com/js/36689.html

    猜你在找的JavaScript相关文章