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