-
js 實(shí)現(xiàn)幀動(dòng)畫原理
查看全部 -
img src
div background
查看全部 -
和上一個(gè)差別不大
Animation.prototype.changeSrc = function (el,imgList) {
var me = this;
var len = imgList.length;
var taskFn;
var type;
if (len) {
taskFn = function (next, time) {
var index = Math.min(time / me.interval | 0 , len-1);
el.src = imageList[index]
//結(jié)束時(shí)跳到下一個(gè)
if (index === len - 1) {
next();
}
}
type = TASK_ASYNC;
} else {
//這里的next就是全局方法 next
taskFn = next;
type = TASK_SYNC;
}
return this._add(taskFn,type)
}
查看全部 -
接下來就是具體用timeline的各個(gè)接口了。
Animation.prototype.changePosition = function (el, positions, imageUrl) {
var me = this;
var len = position.length;
var taskFn;
var type;
if (len) {
taskFn = function (next, time) {
//next, time 是._asyncTask的時(shí)候,傳過來的
//如果傳入了圖片地址,那么修改el的背景圖
if (imageUrl) {
el.style.backgoundImage = 'url(' + imageUrl + ')';
}
//如果當(dāng)前已經(jīng)執(zhí)行的回調(diào)次數(shù) 還沒到最大值( 設(shè)定中的動(dòng)畫執(zhí)行次數(shù))
//那么選擇當(dāng)前已經(jīng)執(zhí)行的回調(diào)次數(shù)作為索引(從0開始數(shù),所以可以直接用)
// |0 既 Math.floor
var index = Math.min(time / me.interval | 0, len - 1)
//是這樣[ 200 332 , 333 33,]的格式
var position = positions[index].split(' ')
el.style.backgoundPosition = position[0]+'px' +' '+ position[1]+'px'
//動(dòng)畫循環(huán)完成了的時(shí)候。
if (index === len - 1) {
next();
}
}
type = TASK_ASYNC;
} else {
//這里的next就是全局方法 next
taskFn = next;
type = TASK_SYNC;
}
return this._add(taskFn,type)
}
function next(callback) {
callback && callback()
}
//異步方法是這樣的:
Animation.prototype._asyncTask = function(task) {
//設(shè)置timeline里面的每幀都調(diào)用的回調(diào)函數(shù)onenterframe。
var enterFrame = function (time) {
var me = this;
var taskFn = task.taskFn;
//回調(diào)函數(shù)的設(shè)置
var next = function () {
//停止當(dāng)前的
me.timeline.stop()
//執(zhí)行下一個(gè)
me._next()
}
taskFn(next,time)
}
this.timeline.onenterframe = enterFrame;
//通過this.prototype.start傳進(jìn)來的參數(shù)Interval傳給timeline
this.timeline.start(this.interval)
}
查看全部 -
創(chuàng)建Timeline實(shí)例,然后用在異步方法里。
function Animation() {
this.timeline = new Timeline();
//來自用戶設(shè)定的時(shí)間間隔
this.interval = 0;
}
Animation.prototype._asyncTask = function(task) {
//設(shè)置timeline里面的每幀都調(diào)用的回調(diào)函數(shù)onenterframe。
var enterFrame = function (time) {
var me = this;
var taskFn = task.taskFn;
//回調(diào)函數(shù)的設(shè)置
var next = function () {
//停止當(dāng)前的
me.timeline.stop()
//執(zhí)行下一個(gè)
me._next()
}
taskFn(next,time)
}
this.timeline.onenterframe = enterFrame;
//通過this.prototype.start傳進(jìn)來的參數(shù)Interval傳給timeline
this.timeline.start(this.interval)
}
查看全部 -
我的筆記代碼并不是完全和視頻一致的。
這一小節(jié)有趣的一點(diǎn)是做了setInterval的迭代式形式。 nextTick函數(shù),其中有requestAnimationFrame(nextTick)。
其中不斷更新記錄上一幀結(jié)束的時(shí)間lastTick。
而這樣的話,lastTick- startTime /interval 大概就是過了多少幀了。
代碼:
function startTimeline(timeline, startTime) {
//設(shè)置實(shí)例上的數(shù)據(jù),儲(chǔ)存用
timeline.startTime = startTime
//為用requestAnimationFrame加上的callback.interval
nextTick.interval = timeline.interval
//記錄最后一次回調(diào)的時(shí)間戳
var lastTick = +new Date();
nextTick();
//+new Date() == new Date().getTime()
//其實(shí)這是一個(gè)迭代形式的setInterval
//每一幀都執(zhí)行的函數(shù)哦
function nextTick() {
//判斷如果時(shí)間到interval設(shè)定了的時(shí)間了,就執(zhí)行回調(diào),
var now = +new Date();
timeline.animationHandler = requestAnimationFrame(nextTick)
if (now - lastTick >= timeline.interval) {
timeline.onenterframe(now - startTime)
//并且更新最后一次回調(diào)的時(shí)間
lastTick = now;
}
}
}
類:?
function Timeline(interval) {
//當(dāng)前動(dòng)畫的狀態(tài)。
this.state = STATE_INITIAL;
//當(dāng)前動(dòng)畫進(jìn)行時(shí)間。
this.startTime = 0;
//每次回調(diào)的時(shí)間間隔。
this.interval = DEFAULT_INTERVAL;
//setTimeout的ID
this.animationHandler = 0;
//動(dòng)畫開始了多久,暫停的時(shí)候儲(chǔ)存留待再次開始
this.dur = 0;
}
/**
?* 動(dòng)畫停止
?*/
Timeline.prototype.stop = function (interval) {
if (this.state !== STATE_START) {
return
}
this.state = STATE_STOP;
//如果動(dòng)畫已經(jīng)開始了,那么記錄一下已經(jīng)開始多久了。
if (this.stateTime) {
this.dur = +new Date() - this.startTime
}
cancelAnimationFrame(this.animationHandler)
}
/**
* 重新播放動(dòng)畫
*/
Timeline.prototype.restart = function (interval) {
if (this.state !== STATE_START) {
return
}
if (!this.dur || this.interval) {
return
}
this.state = STATE_START
//從停止那一刻算起,開始動(dòng)畫
startTimeline(this, +new Date() - this.dur)
}
查看全部 -
這里是定義具體執(zhí)行異步函數(shù)的方法。
首先是處理window.requestAnimationFrame 和window.cancelAnimationFrame的兼容性問題。這里使用的自執(zhí)行函數(shù),通過 || 返回經(jīng)過類型轉(zhuǎn)換后為true的值。如果不支持這個(gè)方法,就用setTimeout,如果用setTimeout,默認(rèn)的時(shí)間間隔是1000/60毫秒。
代碼:
var DEFAULT_INTERVAL = 1000/60
//requestAnimationFrame每17毫秒會(huì)刷新一次。
var requestAnimationFrame = (function () {
//瀏覽器兼容
return window.requestAnimationFrame ||
window.webketRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
//如果不支持,則用setTimeout ,默認(rèn)為 1000/60 毫秒后
function (callback) {
return window.setTimeout(callback(),callback.interval || DEFAULT_INTERVAL)
}
})()
var cancelAnimationFrame = (function () {
return window.cancelAnimationFrame ||
window.webketCancelAnimationFrame ||
window.mozCancelAnimationFrame ||
window.oCancelAnimationFrame ||
//如果不支持,則用setTimeout ,默認(rèn)為 1000/60 毫秒后
function (id) {
return window.clearTimeout(id)
}
})()
接下來定義class。
查看全部 -
總結(jié):
這一個(gè)小節(jié)的內(nèi)容,是把需要的任務(wù)方法添加到任務(wù)隊(duì)列,然后將任務(wù)按照不同類型處理。
這里需要注意的是,每一個(gè)任務(wù)函數(shù)都要有處理callback的能力,這樣才能next;
第一個(gè)任務(wù),將任務(wù)添加到任務(wù)隊(duì)列,是這樣做的。在使用這個(gè)庫的時(shí)候,demo會(huì)首先調(diào)用Animation.prototype.loadImage()這個(gè)方法。而在代碼中,并不是直接調(diào)用預(yù)加載loadImage()方法,而是把這個(gè)方法定義為對(duì)象中的一個(gè)屬性,并定義它的類型,然后將完成的對(duì)象加入一個(gè)列表中,并且返回this。
這個(gè)列表就是this.taskQueue.
格式: [{taskFn: taskFn,
????????????type: type}]
Animation.prototype._add 將任務(wù)加入到任務(wù)隊(duì)列中,
最終this._add返回this。這個(gè)this也被Animation.prototype.loadImage()最終返回。
代碼:
Animation.prototype.loadImage = function (imageList) {
var taskFn = function (next) {
//為了保證不影響原對(duì)象。使用slice
loadImage(imageList.slice(),next)
}
//設(shè)置為同步任務(wù)。
var type = TASK_SYNC;
//放到任務(wù)隊(duì)列中,同時(shí)返回由this._add()所返回的this
return this._add(taskFn, type);
}
Animation.prototype._add = function (taskFn, type) {
this.taskQueue.push({
taskFn: taskFn,
type: type
})
return this;
}
任務(wù)隊(duì)列taskQueue已經(jīng)有了。接下來就是執(zhí)行里面的任務(wù)了。
通過調(diào)用Animation.prototype.start(),
設(shè)置了任務(wù)執(zhí)行狀態(tài),以及時(shí)間間隔之后
這里調(diào)用的是,Animation.prototype._runTasks
_runTasks中,遍歷并沒有用for 語句,而是通過迭代。
代碼:
Animation.prototype.start = function (interval) {
//當(dāng)開始的時(shí)候,如果已經(jīng)開始了,那么不動(dòng)作
if (this.state === STATE_START) {
return this;
}
//如果沒有任務(wù)
if (!this.taskQueue, length) {
return this;
}
this.state = STATE_START;
this.interval = interval;
this._runTasks();
return this;
}
通過在this上定義一個(gè)值為0的index屬性,index會(huì)在完成任務(wù)的時(shí)候遞增,并且在Index == 任務(wù)隊(duì)列的長(zhǎng)度的時(shí)候,也就是執(zhí)行完所有任務(wù)的時(shí)候,停止執(zhí)行,釋放內(nèi)存等。
代碼:
function Animation() {
//任務(wù)隊(duì)列
this.taskQueue = [];
//控制遍歷的計(jì)數(shù)器
this.index = 0;
//執(zhí)行狀態(tài)
this.state = STATE_INITIAL
}
使用this.taskQueue[this.index]的形式取出當(dāng)前方法。
index每次遞增都會(huì)重新調(diào)動(dòng)任務(wù)隊(duì)列的執(zhí)行函數(shù)_runTasks
_runTasks里面的邏輯是,
通過之前this._add的時(shí)候,設(shè)置的方法狀態(tài),來確定是異步還是同步。
代碼:
Animation.prototype._runTasks = function() {
//所以呢,它應(yīng)該是接收一個(gè)任務(wù)隊(duì)列,然后按照異步或者同步的方式分別執(zhí)行。
//在這里這個(gè)任務(wù)隊(duì)列在實(shí)例上的taskQueue
if (!this.taskQueue.length || this.state !== STATE_START ) {
return;
}
//任務(wù)執(zhí)行完畢,釋放內(nèi)存
if (this.index === this.taskQueue.length) {
this.dispose()
return;
}
//由于由this.index控制遍歷,所以從當(dāng)前任務(wù)開始,this.index默認(rèn)為第一個(gè)
//這里已經(jīng)是經(jīng)過this._add后,對(duì)象的形式{taskFn: taskFn, type: type}
var task = this.taskQueue[this.index]
//task可能是同步任務(wù)或者異步任務(wù),兩種任務(wù)執(zhí)行方式不同
if (task.type === TASK_SYNC) {
this._syncTask(task)
} else if (task.type === TASK_ASYNC) {
this._asyncTask(task)
}
}
這里this.index++,以及_runTasks的再次調(diào)用,是在同步或者異步任務(wù)執(zhí)行完畢之后,由負(fù)責(zé)同步任務(wù)或者異步任務(wù)的函數(shù)來調(diào)用的。
代碼:
/*執(zhí)行同步任務(wù)的方法 */
Animation.prototype._syncTask = function (task) {
//保留this,因?yàn)闀?huì)在閉包中調(diào)用。
var m = this
//執(zhí)行完畢的回調(diào)
var next = function () {
me._next()
}
//取出要執(zhí)行的方法
var taskFn = task.taskFn;
//執(zhí)行它,并傳入回調(diào)。
taskFn(next)
}
/*
*切換到下一個(gè)任務(wù)的方法
*/
Animation.prototype._next = function () {
this.index++;
//這里是遍歷。
this._runTasks();
}
/**
* 異步任務(wù)執(zhí)行方法
*/
Animation.prototype._asyncTask = function(task) {
//未待續(xù)完,使用requestAnimationFrame
}
Animation.prototype._runTasks 負(fù)責(zé)確定極限值的收尾工作,以及確定任務(wù)狀態(tài)并且執(zhí)行負(fù)責(zé)同步任務(wù)或者負(fù)責(zé)異步任務(wù)的函數(shù)。
所有的數(shù)據(jù)都由實(shí)例來負(fù)責(zé)。在實(shí)例上定義。
查看全部 -
總結(jié)下,大概是這樣的邏輯
預(yù)加載圖片流程
由定義在proptotype上的LoadImage方法把數(shù)組交給真正去加載圖片的組件模塊。該數(shù)組為[{src:'imageurl'}] 這樣的格式。
經(jīng)由該模塊之后,會(huì)返回一個(gè)數(shù)組,其中標(biāo)志了哪些圖片加載成功,哪些沒有。為了這樣做的話,就需要給圖片分別加一個(gè)對(duì)應(yīng)ID。
在這個(gè)模塊中,會(huì)將數(shù)組中的對(duì)象遍歷取出來加載。為了這樣做,需要先排除錯(cuò)誤數(shù)據(jù),。不存在可以遍歷的對(duì)象,該對(duì)象不存在src,以及在Prototype上的屬性,如果是string的話,做類型轉(zhuǎn)換成Object。
遍歷是用for in 語句。
????????for (var key in images) {}
????????在真正的加載過程中,首先要新建Image對(duì)象。將其綁定到window上。像前面說的,為Image加上ID,同時(shí)數(shù)據(jù)中的對(duì)象也要保存這個(gè)ID。
????????設(shè)置onload, onerror的處理后,設(shè)置Image對(duì)象的src,就會(huì)加載了。
而為了全部加載成功之后之后調(diào)用callback回調(diào)函數(shù),需要進(jìn)行計(jì)數(shù)和確定完成的方法。
在這里是遍歷的時(shí)候count ++ ,每次加載完成,無論失敗與否,都會(huì) --count ,
當(dāng)然失敗與否在該對(duì)象上進(jìn)行的標(biāo)記不同,這里用
status 為loaded 作為成功。status 為error作為失敗
最后當(dāng)沒有圖片可以加載 的時(shí)候,就可以加載回調(diào)函數(shù)了。
當(dāng)然還有對(duì)加載超時(shí)的優(yōu)化。
這里我覺得比較特別的是,并沒有用var timer 而用了 timoutId 這個(gè)變量設(shè)置setTimeout
這里分為兩種情況,所以最開始也設(shè)置了一個(gè)局部變量作為成功或者失敗的標(biāo)志位。var success
如果加載成功,那么success為true, clearTimeout
如果所有的遍歷和加載都完成了,而且success == false 的情況,當(dāng)然就是setTimeout了
查看全部 -
https://github.com/ustbhuangyi/animation/tree/gh-pages/images
查看全部 -
js實(shí)現(xiàn)幀動(dòng)畫
查看全部 -
幀動(dòng)畫原理
查看全部 -
老師聲音好好聽(≧?≦)講得好好查看全部
-
鏈?zhǔn)秸{(diào)用視作任務(wù)鏈查看全部
-
調(diào)用方式查看全部
-
編程接口3查看全部
-
編程接口2查看全部
-
編程接口查看全部
-
需求分析查看全部
-
設(shè)計(jì)過程查看全部
舉報(bào)