国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

javascript幀動畫(實例講解)

2019-11-19 15:34:44
字體:
來源:轉載
供稿:網友

前面的話

幀動畫就是在“連續的關鍵幀”中分解動畫動作,也就是在時間軸的每幀上逐幀繪制不同的內容,使其連續播放而成的動畫。由于是一幀一幀的畫,所以幀動畫具有非常大的靈活性,幾乎可以表現任何想表現的內容。本文將詳細介紹javascript幀動畫

概述

【分類】

常見的幀動畫的方式有三種,包括gif、CSS3 animation和javascript

git和CSS3 animation不能靈活地控制動畫的暫停和播放、不能對幀動畫做更加靈活地擴展。另外,gif圖不能捕捉動畫完成的事件。所以,一般地,使用javascript來實現幀動畫

【原理】

js實現幀動畫有兩種實現方式

1、如果有多張幀動畫圖片,可以用一個image標簽去承載圖片,定時改變image的src屬性(不推薦)

2、把所有的動畫關鍵幀都繪制在一張圖片里,把圖片作為元素的background-image,定時改變元素的background-position屬性(推薦)

因為第一種方式需要使用多個HTTP請求,所以一般地推薦使用第二種方式

【實例】

下面是使用幀動畫制作的一個實例

<div id="rabbit" ></div> <button id="btn">暫停運動</button> <script>var url = 'rabbit-big.png';var positions = ['0,-854','-174 -852','-349 -852','-524 -852','-698 -852','-873 -848'];var ele = document.getElementById('rabbit');var oTimer = null;btn.onclick = function(){ if(btn.innerHTML == '開始運動'){  frameAnimation(ele,positions,url);  btn.innerHTML = '暫停運動'; }else{  clearTimeout(oTimer);  btn.innerHTML = '開始運動'; } }frameAnimation(ele,positions,url);function frameAnimation(ele,positions,url){ ele.style.backgroundImage = 'url(' + url + ')'; ele.style.backgroundRepeat = 'no-repeat';  var index = 0; function run(){  var pos = positions[index].split(' ');  ele.style.backgroundPosition = pos[0] + 'px ' + pos[1] + 'px';  index++;  if(index >= positions.length){   index = 0;  }  oTimer = setTimeout(run,80); } run();} </script>

通用幀動畫

下面來設計一個通用的幀動畫庫

【需求分析】

  1、支持圖片預加載

  2、支持兩種動畫播放方式,及自定義每幀動畫

  3、支持單組動畫控制循環次數(可支持無限次)

  4、支持一組動畫完成,進行下一組動畫

  5、支持每個動畫完成后有等待時間

  6、支持動畫暫停和繼續播放

  7、支持動畫完成后執行回調函數

【編程接口】

1、loadImage(imglist)//預加載圖片

2、changePosition(ele,positions,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方法,執行下一個任務,同時更新任務鏈索引值

【接口定義】

'use strict';/* 幀動畫庫類 * @constructor */function FrameAnimation(){}/* 添加一個同步任務,去預加載圖片 * @param imglist 圖片數組 */FrameAnimation.prototype.loadImage = function(imglist){}/* 添加一個異步定時任務,通過定時改變圖片背景位置,實現幀動畫 * @param ele dom對象 * @param positions 背景位置數組 * @param imageUrl 圖片URL地址 */FrameAnimation.prototype.changePosition = function(ele,positions,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

'use strict';/** * 預加載圖片函數 * @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

'use strict';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實現的完整代碼

'use strict';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,positions,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打包為一個文件

module.exports = { entry:{  animation:"./src/animation.js" }, output:{  path:__dirname + "/build",  filename:"[name].js",  library:"animation",  libraryTarget:"umd", }}

下面是一個代碼實例,通過創建的幀動畫庫實現博客開始的動畫效果

<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Document</title></head><body><div id="rabbit" ></div> <script src="../build/animation.js"></script> <script>var imgUrl = 'rabbit-big.png';var positions = ['0,-854','-174 -852','-349 -852','-524 -852','-698 -852','-873 -848'];var ele = document.getElementById('rabbit');var animation = window.animation;var repeatAnimation = animation().loadImage([imgUrl]).changePosition(ele,positions,imgUrl).repeatForever();repeatAnimation.start(80); </script></body></html>

更多實例

除了可以實現兔子推車的效果,還可以使用幀動畫實現兔子勝利和兔子失敗的效果

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Document</title><style>div{position:absolute;width:102px;height:80px;background-repeat:no-repeat;} </style></head><body><div id="rabbit1" ></div><div id="rabbit2" ></div><div id="rabbit3" ></div> <script type="text/javascript" src="http://sandbox.runjs.cn/uploads/rs/26/ddzmgynp/animation.js"></script><script>var baseUrl = 'http://7xpdkf.com1.z0.glb.clouddn.com/runjs/img/';var images = ['rabbit-big.png','rabbit-lose.png','rabbit-win.png'];for(var i = 0; i < images.length; i++){ images[i] = baseUrl + images[i];}var rightRunningMap = ["0 -854", "-174 -852", "-349 -852", "-524 -852", "-698 -851", "-873 -848"];var leftRunningMap = ["0 -373", "-175 -376", "-350 -377", "-524 -377", "-699 -377", "-873 -379"];var rabbitWinMap = ["0 0", "-198 0", "-401 0", "-609 0", "-816 0", "0 -96", "-208 -97", "-415 -97", "-623 -97", "-831 -97", "0 -203", "-207 -203", "-415 -203", "-623 -203", "-831 -203", "0 -307", "-206 -307", "-414 -307", "-623 -307"];var rabbitLoseMap = ["0 0", "-163 0", "-327 0", "-491 0", "-655 0", "-819 0", "0 -135", "-166 -135", "-333 -135", "-500 -135", "-668 -135", "-835 -135", "0 -262"];var animation = window.animation;function repeat(){ var repeatAnimation = animation().loadImage(images).changePosition(rabbit1, rightRunningMap, images[0]).repeatForever(); repeatAnimation.start(80); }function win() { var winAnimation = animation().loadImage(images).changePosition(rabbit2, rabbitWinMap, images[2]).repeatForever(); winAnimation.start(200);}function lose() { var loseAnimation = animation().loadImage(images).changePosition(rabbit3, rabbitLoseMap, images[1]).repeatForever(); loseAnimation.start(200);}repeat();win();lose();</script></body></html>

以上這篇javascript幀動畫(實例講解)就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 鹤山市| 中方县| 偏关县| 清远市| 洛南县| 广东省| 邯郸市| 常熟市| 洞口县| 阿坝| 边坝县| 泾源县| 九龙县| 华亭县| 儋州市| 贺州市| 黎城县| 中山市| 冕宁县| 通化县| 孝感市| 英吉沙县| 贵港市| 阿拉尔市| 湖北省| 鄯善县| 建水县| 红河县| 黄冈市| 辽中县| 曲周县| 田东县| 丰县| 黔江区| 鹤壁市| 肃宁县| 柘城县| 吐鲁番市| 车致| 芜湖市| 宁阳县|