下面小編就為大家帶來一篇javascript幀動畫(實例講解)。小編覺得蠻不錯的,現在就分享給大家,也給大家做個參考。一起跟著小編過來看看吧
前面的話
#幀動畫就是在「連續的關鍵影格」中分解動畫動作,也就是在時間軸的每個畫面上逐格繪製不同的內容,使其連續播放而成的動畫。由於是一幀一幀的畫,所以幀動畫具有非常大的靈活性,幾乎可以表現任何想表現的內容。本文將詳細介紹javascript幀動畫
概述
#【分類】
常見的幀動畫的方式有三種,包括gif、CSS3 animation和javascript
git和CSS3 animation不能靈活地控制動畫的暫停和播放、不能對幀動畫做更加靈活地擴展。另外,gif圖不能捕捉動畫完成的事件。所以,一般地,使用javascript來實作幀動畫
【原理】
js實作幀動畫有兩種實作方式
##1、如果有多張幀動畫圖片,可以用一個image標籤去承載圖片,定時改變image的src屬性(不推薦)2、把所有的動畫關鍵幀都繪製在一張圖片裡,把圖片作為元素的background-image,定時改變元素的background-position屬性(推薦)因為第一種方式需要使用多個HTTP請求,所以一般地推薦使用第二種方式【實例】下面是使用幀動畫製作的一個實例
#
<p id="rabbit" ></p> <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()//釋放資源
【調用方式】
連結鍊式調用,用動詞的方式描述介面
【程式碼設計】
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設定
#
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> <p id="rabbit" ></p> <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> p{position:absolute;width:102px;height:80px;background-repeat:no-repeat;} </style> </head> <body> <p id="rabbit1" ></p> <p id="rabbit2" ></p> <p id="rabbit3" ></p> <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幀動畫的詳細內容。更多資訊請關注PHP中文網其他相關文章!

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

ZendStudio 13.5.1 Mac
強大的PHP整合開發環境

PhpStorm Mac 版本
最新(2018.2.1 )專業的PHP整合開發工具

SecLists
SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

DVWA
Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

VSCode Windows 64位元 下載
微軟推出的免費、功能強大的一款IDE編輯器