js動(dòng)畫

看了很多視頻、文章棺克,最后卻通通忘記了,別人的知識(shí)依舊是別人的,自己卻什么都沒獲得派草。此系列文章旨在加深自己的印象,因此此系列文章大都是將別人的文章連復(fù)制帶寫而來簸州,若有侵權(quán),請(qǐng)及時(shí)通知矢洲,必定立即刪除袁滥。

其實(shí)Web動(dòng)畫的實(shí)現(xiàn)原理跟早期的運(yùn)動(dòng)影片很類似,都是通過將一張張的賽璐珞片以較快速度播放藐握,從而模擬出連貫的物體運(yùn)動(dòng)。而這一張張的賽璐珞片就類似于投影運(yùn)動(dòng)媒體的幀的概念初家,而幾乎所有投影運(yùn)動(dòng)媒體都是通過幀來實(shí)現(xiàn)的

幾乎所有的程序動(dòng)畫都會(huì)表現(xiàn)為某種形式的循環(huán),我們會(huì)創(chuàng)建一個(gè)展現(xiàn)一系列圖像的流程圖以實(shí)現(xiàn)逐幀動(dòng)畫,其中每一幀只需要繪制出來即可志笼。

為了實(shí)現(xiàn)動(dòng)畫韧掩,需要為每一幀執(zhí)行以下操作:

  1. 執(zhí)行該幀所要調(diào)用到的代碼坊谁;

  2. 將所有對(duì)象繪制到出來简珠;

  3. 重復(fù)這一過程渲染下一幀

下面主要討論一下JavaScript中動(dòng)畫循環(huán)函數(shù) —— setTimeout()、setInterval()和requestAnimationFrame()聋庵,他們的對(duì)應(yīng)取消循環(huán)函數(shù)分別是clearTimeout()膘融、clearInterval()和cancelAnimationFrame()

setTimeout實(shí)現(xiàn)循環(huán)動(dòng)畫的原理:

(function drawFrame() {
    var timer = null;
    var delayTime = 1000 / 60;
    // 幀渲染和幀繪制 ...
    timer = setTimeout(drawFrame, delayTime);
    // 停止循環(huán)
    if( /* 停止條件成立 */ ) {
        clearTimeout(timer);
    }
})();

setInterval()

var timer = null;
var delayTime = 1000/ 60;
timer = setInterval(drawFrame, delayTime);

function drawFrame() {
    // 幀渲染和幀繪制 ...
    // 停止循環(huán)
    if( /* 動(dòng)畫停止條件成立 */ ) {
        clearInterval(timer);
    }
}

setInterval卻沒有被所調(diào)用的函數(shù)所束縛,它只是簡(jiǎn)單地每隔一定時(shí)間就重復(fù)執(zhí)行一次所調(diào)用的函數(shù)祭玉。而setTimeout受所調(diào)用函數(shù)的影響氧映,只有執(zhí)行完成該次的函數(shù)調(diào)用,才能繼續(xù)執(zhí)行下一次的函數(shù)調(diào)用脱货。

如果要求在每隔一個(gè)固定的時(shí)間間隔后就精確地執(zhí)行某動(dòng)作,那么最好使用setInterval凤价。

如果不想由于連續(xù)調(diào)用產(chǎn)生互相干擾的問題,尤其是每次函數(shù)的調(diào)用需要繁重的計(jì)算以及很長(zhǎng)的處理時(shí)間,那么最好使用setTimeout

requestAnimationFrame()的原理其實(shí)與setTimeout和setInterval類似澈段,通過遞歸調(diào)用同一方法來不斷更新畫面以達(dá)到動(dòng)畫效果芬骄,但它優(yōu)于setTimeout和setInterval的地方在于它是由瀏覽器專門為動(dòng)畫提供優(yōu)化實(shí)現(xiàn)的API蒲牧,并且充分利用顯示器的刷新機(jī)制缓升,比較節(jié)省系統(tǒng)資源燥狰。顯示器有固定的刷新頻率(60Hz或75Hz)嗤练,也就是說遮婶,每秒最多只能重繪60次或75次,requestAnimationFrame的基本思想就是與這個(gè)刷新頻率保持同步排截,利用這個(gè)刷新頻率進(jìn)行頁面重繪垦垂。此外差购,使用這個(gè)API彰居,一旦頁面不處于瀏覽器的當(dāng)前標(biāo)簽杯缺,就會(huì)自動(dòng)停止刷新。這就節(jié)省了CPU、GPU和電力唉俗。

不過有一點(diǎn)需要注意,requestAnimationFrame是在主線程上完成。這意味著得滤,如果主線程非常繁忙慷暂,requestAnimationFrame的動(dòng)畫效果會(huì)大打折扣。

requestAnimationFrame的語法如下:

requestAnimationFrame(callback) //callback為回調(diào)函數(shù)

requestAnimationFrame動(dòng)畫的實(shí)現(xiàn)原理與setTimeout類似陨舱,都是使用一個(gè)回調(diào)函數(shù)作為參數(shù)滑燃,且這個(gè)回調(diào)函數(shù)會(huì)在瀏覽器重繪之前調(diào)用。具體如下

(function drawFrame() {
    var timer = null;
    // 幀渲染和幀繪制 ...
    timer = requestAnimationFrame(drawFrame);
    // 停止循環(huán)
    if( /* 停止條件成立 */ ) {
        cancelAnimationFrame(timer);
    }
})();
var bind = (function(ele, eventType, callback) {
    if(ele.addEventListener) {
        // W3C標(biāo)準(zhǔn)寫法
        return ele.addEventListener(eventType, callback, false);
    }else if(ele.attachEvent) {
        // 兼容IE6~8
        return ele.attachEvent(eventType, callback);
    }else {
        // 兼容IE5-
        return ele["on" + eventType] = callback;
    }
})();

var unbind = (function(ele, eventType, callback) {
    if(ele.removeEventListener) {
        // W3C標(biāo)準(zhǔn)寫法
        return ele.removeEventListener(eventType, callback, false);
    }else if(ele.detachEvent) {
        // 兼容IE6~8
        return ele.detachEvent(eventType, callback);
    }else {
        // 兼容IE5-
        return ele["on" + eventType] = null;
    }
})();

常見的事件類型

鼠標(biāo)事件:

onmousedown, onmouseup, onclick, ondbclick, onmousewheel, onmousemove, onmouseover, onmouseout颓鲜;

觸摸事件:

ontouchstart, ontouchend, ontouchmove表窘;

鍵盤事件:

onkeydown, onkeyup, onkeypress;

頁面相關(guān)事件:

onabort(圖片在下載時(shí)被用戶中斷), onbeforeunload(當(dāng)前頁面的內(nèi)容將要被改變時(shí)觸發(fā)), onerror(出現(xiàn)錯(cuò)誤時(shí)觸發(fā)), onload(內(nèi)容加載完成時(shí)觸發(fā)), onmove(瀏覽器窗口被移動(dòng)時(shí)觸發(fā)), onresize(瀏覽器的窗口大小被改變時(shí)觸發(fā)), onscroll(滾動(dòng)條位置發(fā)生變化時(shí)觸發(fā)), onstop(瀏覽器的停止按鈕被按下時(shí)觸發(fā)此事件或者正在下載的文件被中斷時(shí)觸發(fā)), onunload(當(dāng)前頁面將被改變時(shí)觸發(fā))甜滨;

表單相關(guān)事件

onblur(元素失去焦點(diǎn)時(shí)觸發(fā)), onchange(元素失去焦點(diǎn)且元素內(nèi)容發(fā)生改變時(shí)觸發(fā)), onfocus(元素獲得焦點(diǎn)時(shí)觸發(fā)), onreset(表單中reset屬性被激活時(shí)觸發(fā)), onsubmit(表單被提交時(shí)觸發(fā))乐严;oninput(在input元素內(nèi)容修改后立即被觸發(fā),兼容IE9+)

編輯事件

onbeforecopy:當(dāng)頁面當(dāng)前的被選擇內(nèi)容將要復(fù)制到瀏覽者系統(tǒng)的剪貼板前觸發(fā)此事件衣摩;

onbeforecut:當(dāng)頁面中的一部分或者全部的內(nèi)容將被移離當(dāng)前頁面[剪貼]并移動(dòng)到瀏覽者的系統(tǒng)剪貼板時(shí)觸發(fā)此事件昂验;

onbeforeeditfocus:當(dāng)前元素將要進(jìn)入編輯狀態(tài);

onbeforepaste:內(nèi)容將要從瀏覽者的系統(tǒng)剪貼板傳送[粘貼]到頁面中時(shí)觸發(fā)此事件艾扮;

onbeforeupdate:當(dāng)瀏覽者粘貼系統(tǒng)剪貼板中的內(nèi)容時(shí)通知目標(biāo)對(duì)象凛篙;

oncontextmenu:當(dāng)瀏覽者按下鼠標(biāo)右鍵出現(xiàn)菜單時(shí)或者通過鍵盤的按鍵觸發(fā)頁面菜單時(shí)觸發(fā)的事件;

oncopy:當(dāng)頁面當(dāng)前的被選擇內(nèi)容被復(fù)制后觸發(fā)此事件栏渺;

oncut:當(dāng)頁面當(dāng)前的被選擇內(nèi)容被剪切時(shí)觸發(fā)此事件呛梆;

onlosecapture:當(dāng)元素失去鼠標(biāo)移動(dòng)所形成的選擇焦點(diǎn)時(shí)觸發(fā)此事件;

onpaste:當(dāng)內(nèi)容被粘貼時(shí)觸發(fā)此事件磕诊;

onselect:當(dāng)文本內(nèi)容被選擇時(shí)的事件填物;

onselectstart:當(dāng)文本內(nèi)容選擇將開始發(fā)生時(shí)觸發(fā)的事件;

拖動(dòng)事件

ondrag:當(dāng)某個(gè)對(duì)象被拖動(dòng)時(shí)觸發(fā)此事件 [活動(dòng)事件]霎终;

ondragdrop:一個(gè)外部對(duì)象被鼠標(biāo)拖進(jìn)當(dāng)前窗口時(shí)觸發(fā)滞磺;

ondragend:當(dāng)鼠標(biāo)拖動(dòng)結(jié)束時(shí)觸發(fā)此事件;

ondragenter:當(dāng)對(duì)象被鼠標(biāo)拖動(dòng)的對(duì)象進(jìn)入其容器范圍內(nèi)時(shí)觸發(fā)此事件莱褒;

ondragleave:當(dāng)對(duì)象被鼠標(biāo)拖動(dòng)的對(duì)象離開其容器范圍內(nèi)時(shí)觸發(fā)此事件击困;

ondragover:當(dāng)某被拖動(dòng)的對(duì)象在另一對(duì)象容器范圍內(nèi)拖動(dòng)時(shí)觸發(fā)此事件;

ondragstart:當(dāng)某對(duì)象將被拖動(dòng)時(shí)觸發(fā)此事件;

ondrop:在一個(gè)拖動(dòng)過程中阅茶,釋放鼠標(biāo)鍵時(shí)觸發(fā)此事件蛛枚;

事件的常見應(yīng)用

獲取鼠標(biāo)位置

每個(gè)鼠標(biāo)事件都有兩個(gè)屬性用于確定鼠標(biāo)當(dāng)前位置:pageX和pageY。但是IE6~8不知持這兩個(gè)屬性脸哀,需要用到clientX和clientY蹦浦。

其中,pageX和pageY的鼠標(biāo)位置是相對(duì)于document文檔的撞蜂,而clientX和clientY的鼠標(biāo)位置是相對(duì)于瀏覽器屏幕的盲镶。為了實(shí)現(xiàn)各平臺(tái)統(tǒng)一,兼容性寫法可以如下:

/ 初始化鼠標(biāo)位置蝌诡,這里的鼠標(biāo)位置默認(rèn)是相對(duì)于document文檔的
var mouse = {
    x: 0,
    y: 0
}; 
function getMouse(event) {
    var event = event || window.event;
    if(event.pageX || event.pageY) {
        x = event.x;
        y = event.y;
    }else {
        var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
        var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
        x = event.clientX + scrollLeft;
        y = event.clientY + scrollTop;
    }
    mouse.x = x;
    mouse.y = y;

    return mouse;
}

觸摸位置

一個(gè)觸摸點(diǎn)可以被想象成鼠標(biāo)光標(biāo)溉贿,不過鼠標(biāo)光標(biāo)會(huì)一直停留在屏幕上,而手指卻會(huì)從設(shè)備上按下浦旱、移動(dòng)以及釋放宇色,所以某些時(shí)刻光標(biāo)會(huì)從屏幕上消失。另外闽寡,觸摸屏上不存在mouseover等效的觸摸事件。同一時(shí)間可能發(fā)生多點(diǎn)觸摸尼酿,某個(gè)觸摸點(diǎn)的信息會(huì)保存在觸摸事件的一個(gè)數(shù)組中爷狈。

獲取觸摸位置的方法見下:

// 觸摸位置聲明
var touch = {
    x: null,
    y: null,
    isPress: false
}

function getTouch (event) {
    var x, y, 
    touchEvent = event.touches[0]; //獲取觸摸位置的第一個(gè)觸摸點(diǎn)
    var event = event || window.event;
    if(touchEvent.pageX || touchEvent.pageY) {
        x = touchEvent.pageX;
        y = touchEvent.pageY;
    }else {
        var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
        var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
        x = touchEvent.clientX + scrollLeft;
        y = touchEvent.clientY + scrollTop;
    }
    touch.x = x;
    touch.y = y;

    return touch;
}

常用的方法是,如果不存在有效的觸摸點(diǎn)是裳擎,x和y的值應(yīng)設(shè)置為null涎永。

element.addEventListener("touchstart", function(event) {
    touch.isPressed = true;
}, false);
element.addEventListener("touchsend", function(event) {
    touch.isPressed = false;
    touch.x = null;
    touch.y = null;
}, false);
element.addEventListener("touchsend", function(event) {
    if(touch.isPressed) {
        getTouch (event);
    }
}, false);

獲得鍵盤碼可以使用event.keyCode。具體實(shí)現(xiàn)如下:

var keyCode;
function getKeyCode(event) {
    var event = event || window.event;
    keyCode = event.keyCode;
    return keyCode;
}

多數(shù)Web動(dòng)畫都是由一幀幀的狀態(tài)通過較快速度的播放模擬出來的鹿响,所以循環(huán)計(jì)時(shí)函數(shù)在這里就起到了連接連貫的幀狀態(tài)的作用羡微。而動(dòng)畫更多時(shí)候需要用戶交互,所以事件和事件監(jiān)聽尤顯重要惶我。最后我列出了幾個(gè)經(jīng)常用到幾個(gè)與事件相關(guān)的封裝應(yīng)用妈倔,方便自己查閱和調(diào)用

最普通的動(dòng)畫就是勻速的動(dòng)畫,每次增加固定的值绸贡。但是生活中很多運(yùn)動(dòng)并不是勻速運(yùn)動(dòng)的盯蝴,而是有加速度改變的運(yùn)動(dòng)。在Web動(dòng)畫中听怕,緩動(dòng)動(dòng)畫有時(shí)候會(huì)讓網(wǎng)站增色不少捧挺。

在CSS3中可以使用ease, ease-in, ease-out, ease-in-out 或者 cubic-bezier(n,n,n,n)來實(shí)現(xiàn)緩動(dòng)動(dòng)畫。而且目前也有一些jQuery封裝了緩動(dòng)動(dòng)畫的Move.js, Velocity.js和Tween.js等尿瞭。在實(shí)際項(xiàng)目中使用這些庫文件或者CSS3屬性可以大大提高開發(fā)效率闽烙。但是在學(xué)習(xí)中,為了了解JS緩動(dòng)動(dòng)畫的真正原理声搁,我覺得有必要嘗試用原生的JS實(shí)現(xiàn)之黑竞。

總的來說捕发,緩動(dòng)動(dòng)畫都是把對(duì)象從已有位置移動(dòng)到目標(biāo)位置的過程,在這個(gè)過程中摊溶,加速度或者速度會(huì)隨與目標(biāo)位置的遠(yuǎn)近而變化爬骤。

緩動(dòng)動(dòng)畫的一些具體動(dòng)畫曲線可以查看這里《緩動(dòng)函數(shù)》,感受一下~

一. 一般實(shí)現(xiàn)緩動(dòng)的策略如下:

1 . 為運(yùn)動(dòng)確定一個(gè)比例系數(shù)莫换,這是一個(gè)小于1且大于0的小數(shù)霞玄;

2 . 確定目標(biāo)點(diǎn);

3 . 計(jì)算出物體當(dāng)前位置與目標(biāo)點(diǎn)位置的距離拉岁;

4 . 計(jì)算速度坷剧,例如緩入動(dòng)畫中,速度 = 距離 × 比例系數(shù)喊暖,這時(shí)比例系數(shù)為運(yùn)動(dòng)的加速度惫企;

5 . 用當(dāng)前位置加上速度來計(jì)算新的位置;

6 . 重復(fù)第3到第5步陵叽,知道物體到達(dá)目標(biāo)狞尔;

緩入動(dòng)畫

先看看這些代碼片段以及他們的含義:

1 . 確定一個(gè)小數(shù)作為比例系數(shù),這個(gè)比例系數(shù)為加速度(標(biāo)量)巩掺。當(dāng)系數(shù)越接近于1偏序,物體移動(dòng)得越快;當(dāng)系數(shù)越接近于0胖替,物體移動(dòng)得越慢研儒。

var easing = 0.05;
2 . 確定目標(biāo)點(diǎn)。這里用targetX和targetY來定義:

var targetX = canvas.width / 2,
targetY = canvas.height / 2;
3 . 計(jì)算物體到目標(biāo)點(diǎn)的距離独令。創(chuàng)建小球名為ball缎谷,用ball的x丧肴、y減去目標(biāo)點(diǎn)的x焰络、y就能得到距離鸥咖。

var dx = targetX - ball.x,
dy= targetY - ball.y;
4 . 速度 = 距離 × 比例系數(shù)。

var vx = dx * easing,
vy= dy * easing;
5 . 用當(dāng)前位置加上速度來計(jì)算新的位置招狸。

ball.x += vx;
ball.y += vy;
6 . 因?yàn)樽詈髱撞叫枰貜?fù)執(zhí)行碗硬,所以會(huì)把這些代碼放在drawFrame函數(shù)里面

改進(jìn)版緩入動(dòng)畫:加入拖拽效果

改進(jìn)版緩入動(dòng)畫

二. 何時(shí)停止緩動(dòng)動(dòng)畫

當(dāng)計(jì)算一個(gè)單目標(biāo)點(diǎn)的簡(jiǎn)單緩動(dòng)時(shí),物體最終會(huì)到達(dá)這個(gè)目標(biāo)點(diǎn)瓢颅,緩動(dòng)也就完成了恩尾。但是,即使在前面的幾個(gè)例子里挽懦,即使該物體看起來已經(jīng)停止了翰意,計(jì)算緩動(dòng)動(dòng)畫的代碼還是一直在執(zhí)行(不信的可以在緩動(dòng)動(dòng)畫中加入打印代碼如console.log("hello world!"),打開控制臺(tái)就會(huì)看到健步如飛的"hello world!"會(huì)打印出來~)。這樣比較浪費(fèi)系統(tǒng)資源冀偶。一旦物體到達(dá)了目標(biāo)點(diǎn)醒第,代碼就應(yīng)該不再執(zhí)行了。這個(gè)功能很簡(jiǎn)單进鸠,只需要在動(dòng)畫循環(huán)里面判斷一下物體是否到達(dá)目標(biāo)點(diǎn)即可

if(ball.x === targetX && ball.y === targetY) {
    // 停止緩動(dòng)動(dòng)畫代碼
    window.cancelAnimationFrame(animRequest);
}

事實(shí)上稠曼,由于ball.x和ball.y可能是小數(shù),隨著vx和vy越來越小越趨近于0客年,事實(shí)上它離目標(biāo)點(diǎn)越來越近霞幅,但是理論上永遠(yuǎn)不會(huì)到達(dá)目標(biāo)點(diǎn),而是無窮趨于目標(biāo)點(diǎn)的小數(shù)量瓜。一般分辨率的電腦的顯示的最小精度是1px(除了一些高分屏精度為0.1px)司恳,不能精確顯示無窮多位小數(shù)的距離。到底多近才是足夠近绍傲?這就需要判斷物體到目標(biāo)點(diǎn)的距離是否小于特定值了扔傅。我們可以根據(jù)實(shí)際情況使用Math.ceil()、Math.floor()或Math.round()來對(duì)小數(shù)進(jìn)行取整操作烫饼,以取接近目標(biāo)點(diǎn)的值

if(Math.ceil(ball.x) === targetX && Math.ceil(ball.y) === targetY) {
    // 停止緩動(dòng)動(dòng)畫代碼
    window.cancelAnimationFrame(animRequest);
}

三. 移動(dòng)的目標(biāo)點(diǎn)

在前面的例子中猎塞,目標(biāo)點(diǎn)只有一個(gè),并且是固定的杠纵。

然而目標(biāo)點(diǎn)可以是移動(dòng)的荠耽。我們?cè)诿恳粠紩?huì)重新計(jì)算距離,然后根據(jù)距離計(jì)算速度淡诗,代碼并不關(guān)心物體是否到否目標(biāo)點(diǎn)或者目標(biāo)點(diǎn)是否在移動(dòng)骇塘,它只需在播放的每一幀的時(shí)候知道目標(biāo)點(diǎn)的位置伊履,然后計(jì)算距離和速度韩容。

小球跟隨鼠標(biāo)運(yùn)動(dòng)的例子中,我們把鼠標(biāo)位置作為目標(biāo)點(diǎn)唐瀑,只需要把前面例子中的targetX和targetY分別替換為鼠標(biāo)的位置mouse.x和mouse.y即可

小球跟隨鼠標(biāo)運(yùn)動(dòng)

緩動(dòng)不僅僅適用于運(yùn)動(dòng)群凶,還可以操作很多其他屬性。只要這個(gè)屬性是可以用數(shù)字表示的哄辣,就可以操作它请梢。例如:

4.1 Demo1. 顏色緩動(dòng)動(dòng)畫

嘗試在24位顏色上使用緩動(dòng),要設(shè)置紅力穗、綠毅弧、藍(lán)的初始值和目標(biāo)值,用緩動(dòng)改變每一種單獨(dú)的顏色当窗,然后再把他么合并為單個(gè)顏色

// 初始化變量
var red = 255,
    green = 0,
    blue = 0,
    redTarget = 0,
    greenTarget = 0,
    blueTarget = 255;

// 使用緩動(dòng)動(dòng)畫
red += Math.ceil((redTarget - red) * easing);
green += Math.ceil((greenTarget - green) * easing);
blue += Math.ceil((blueTarget - blue) * easing);

// 最后把這三個(gè)單色值合并成一個(gè)顏色
ball.fillStyle = "rgb(" + red +"," + green + "," + blue + ")";

變顏色

4.2 Demo2. 透明度緩動(dòng)動(dòng)畫

將緩動(dòng)應(yīng)用在alpha上够坐,設(shè)置alpha的初始值和目標(biāo)值,然后使用緩動(dòng)動(dòng)畫實(shí)現(xiàn)淡入淡出的效果,最后把它拼接成一個(gè)RGBA字符串:

var alpha = 0,
targetAlpha = 1;

// 使用緩動(dòng)動(dòng)畫
alpha += (targetAlpha - alpha) * easing;
ball.fillStyle = "rgba(" + red +"," + green + "," + blue + "," + alpha + ")";

demo

五. 高級(jí)緩動(dòng)

我們上面用到的都是簡(jiǎn)單緩動(dòng)元咙,即物體只有一個(gè)加速度easing梯影。而事實(shí)上我們可以完全可以通過使easing為非定值,來實(shí)現(xiàn)自定義物體的任意運(yùn)動(dòng)狀態(tài)庶香,譬如先加速且接近物體時(shí)減速等甲棍。

一些高級(jí)緩動(dòng)函數(shù)可以參考:

1 . Tween.js的源碼:https://github.com/tweenjs/tween.js/blob/master/src/Tween.js

2 . jquery.easing.js的源碼:https://github.com/gdsmith/jquery.easing/blob/master/jquery.easing.js

六. 總結(jié)

緩動(dòng)動(dòng)畫是比例速度,通過修改每一幀的速度來計(jì)算出當(dāng)前值赶掖,通過加速度easing可以控制獨(dú)特的動(dòng)畫效果感猛。簡(jiǎn)單緩動(dòng)動(dòng)畫不難,關(guān)鍵是要?jiǎng)邮志毩?xí)倘零。高級(jí)緩動(dòng)動(dòng)畫唱遭,可以自己實(shí)驗(yàn)出一種特效,或者多看看Tween.js和jquery.easing.js等一些類庫的緩動(dòng)動(dòng)畫實(shí)現(xiàn)以汲取經(jīng)驗(yàn)

彈動(dòng)動(dòng)畫:

緩動(dòng)和彈動(dòng)都是那對(duì)象從已有位置移動(dòng)到目標(biāo)位置的方法呈驶。但是緩動(dòng)是指物體滑動(dòng)到目標(biāo)點(diǎn)就停下來拷泽;而彈動(dòng)是指物體來回反彈一段時(shí)間后,最終停在目標(biāo)點(diǎn)的運(yùn)動(dòng)袖瞻。

彈動(dòng)司致,大多數(shù)時(shí)候,物體的加速度與它到目標(biāo)點(diǎn)的距離是成比例的聋迎。

來看一個(gè)在現(xiàn)實(shí)中彈動(dòng)的例子:在橡皮筋的一頭系上一個(gè)小球脂矫,另一頭固定起來。小球的目標(biāo)點(diǎn)就是它初始靜止懸空的那個(gè)位置點(diǎn)霉晕。將小球拉開一小段距離然后松開庭再,剛松手那一瞬間,它的速度為0牺堰,但是橡皮筋給它施加了外力拄轻,把它拉向目標(biāo)點(diǎn);如果小球盡可能地拉遠(yuǎn)伟葫,橡皮筋對(duì)它施加的外力就會(huì)變得越大恨搓。松手后,小球會(huì)急速飛過目標(biāo)點(diǎn)筏养。但是斧抱,當(dāng)它飛過目標(biāo)點(diǎn)以后,橡皮筋又把它往回拉渐溶,使其加速度減小辉浦,它飛得越遠(yuǎn),橡皮筋施加的力就越大茎辐;最終宪郊,它的速度降為0眉睹,又掉頭往回飛。由于受到摩擦力的影響废膘,反復(fù)幾次后竹海,小球的運(yùn)動(dòng)逐漸慢下來,停在目標(biāo)點(diǎn)上丐黄。

一. 一維坐標(biāo)上的彈動(dòng)

1 . 首先需要一個(gè)變量存儲(chǔ)彈性比例系數(shù)斋配,取值為0~1,較大的彈性比例常熟會(huì)表現(xiàn)出較硬的彈簧效果灌闺。

var spring = 0.1,
targetX = canvas.width / 2,
vx = 0;
2 . 接下來艰争,計(jì)算小球到目標(biāo)點(diǎn)的距離

var dx = targetX - ball.x;
3 . 計(jì)算加速度。在這個(gè)例子中桂对,我們?cè)O(shè)置小球的加速度與距離成正比甩卓,即加速度 = 小球到目標(biāo)點(diǎn)的距離 × 彈性比例系數(shù)。

var ax = dx * spring;
4 . 把加速度累加到速度上蕉斜,然后把速度累加到小球的當(dāng)前位置上:

vx += ax;
ball.x += vx;
在開始寫代碼前逾柿,先模擬一下整個(gè)過程,假設(shè)ball.x = 0宅此,初速度vx = 0机错,目標(biāo)點(diǎn)的位置targetX = 100,彈性比例系數(shù)spring = 0.1父腕。下面是執(zhí)行過程:

(1) 第一輪弱匪,加速度ax = (100 – 0) * 0.1 = 10,把a(bǔ)x加載vx上得速度vx = 10璧亮,把vx加在小球的當(dāng)前位置上得到ball.x = 10萧诫;

(2) 第二輪,加速度ax = (100 – ball.x) * 0.1 = 9枝嘶,由此得到vx = 10 + 9 = 19帘饶,ball.x = 10 + 19 = 29;

(3) 第三輪躬络,ax = 7.1尖奔, vx = 26.1搭儒,ball.x = 55.1穷当;

(4) 第四輪, ax = 4.49淹禾,vx = 30.59馁菜,ball.x = 85.69;

(5) 第五輪, ax = 1.431铃岔,vx = 44.9汪疮,ball.x = 130.69:

(6) 第六輪峭火,ax = -3.069,vx = 41.831智嚷,ball.x = 88.859卖丸;

… …

隨著小球一幀一幀地靠近目標(biāo),加速度變得越來越小盏道,但是速度一直在增加稍浆;

五輪過后,小球越過了目標(biāo)點(diǎn)后猜嘱,加速度變成反向加速度衅枫,并且逐漸增加,導(dǎo)致速度逐漸減小朗伶,最終速度為0后弦撩,反向加速度達(dá)到極大值。此時(shí)速度將變成反向速度论皆。

demo

但是益楼!但是,問題是小球永遠(yuǎn)都不會(huì)停下來点晴,因?yàn)樾∏虻臄[動(dòng)幅度不變偏形。而我們希望實(shí)現(xiàn)的例子中,小球的彈動(dòng)會(huì)越來越慢觉鼻,直到停止下來俊扭。在實(shí)際生活中,小球的彈動(dòng)勢(shì)能大多是由于摩擦力的存在而轉(zhuǎn)化成內(nèi)能坠陈,最后使小球停下萨惑。所以,在這里仇矾,我們也模擬摩擦力庸蔼,創(chuàng)建摩擦力系數(shù)friction,取值范圍為0~1贮匕。

var friction = 0.95;

然后把vx * friction姐仅,得到當(dāng)前的速度vx。

vx * = friction;

demo

二. 二維坐標(biāo)上的彈動(dòng)

上面一個(gè)例子是讓小球在x軸上運(yùn)動(dòng)刻盐。如果我們想讓小球同時(shí)在x軸和y軸上運(yùn)動(dòng)掏膏,就需要引入二維坐標(biāo)上的彈動(dòng)。事實(shí)上很簡(jiǎn)單敦锌,只需要把目標(biāo)點(diǎn)馒疹、速度和加速度擴(kuò)展到二維坐標(biāo)系上即可。

代碼與上面例子雷同不再重復(fù)乙墙,直接上效果:
demo

與前一個(gè)例子唯一不同的是增加了一條y軸颖变。但是現(xiàn)在小球看起來仍然像是一維運(yùn)動(dòng)生均,雖然小球同時(shí)在x軸和y軸上運(yùn)動(dòng),但它仍然是一條直線腥刹。原因是它的初速度為0马胧,也僅受一個(gè)把它拉向目標(biāo)點(diǎn)的外力,所以它沿著直線運(yùn)動(dòng)衔峰。為了動(dòng)畫更豐富一點(diǎn)漓雅,可以嘗試修改vx、vy或者不同x朽色、y軸的friction值邻吞。自己嘗試一下吧。

三. 目標(biāo)點(diǎn)移動(dòng)的彈動(dòng)

目標(biāo)點(diǎn)移動(dòng)葫男,我們很容易就想到把鼠標(biāo)當(dāng)成目標(biāo)點(diǎn)抱冷。在上一篇介紹緩動(dòng)動(dòng)畫時(shí),有一個(gè)小球跟隨鼠標(biāo)的緩動(dòng)動(dòng)畫梢褐。讓小球跟隨鼠標(biāo)彈動(dòng)同樣很簡(jiǎn)單旺遮,只要把targetX和targetY替換為當(dāng)前坐標(biāo)即可。效果很炫酷盈咳,但是代碼基本沒變耿眉。只要在前面的例子中改動(dòng)如下兩行:

var dx = targetX - ball.x;
var dy = targetY - ball.y;

修改為:

var dx = mouse.x - ball.x;
var dy = mouse.y - ball.y;

demo

好吧,上面這個(gè)例子不夠帶勁兒鱼响,希望使小球看起來像是栓在橡皮筋上鸣剪,此時(shí)只要在上面的基礎(chǔ)上再小球圓心與當(dāng)前鼠標(biāo)位置畫線即可。

context.beginPath();
context.strokeStyle = "#71A4AD";
context.moveTo(ball.x, ball.y);
context.lineTo(mouse.x, mouse.y);
context.stroke();

demo

彈動(dòng)和緩動(dòng)非常類似丈积,都是使用循環(huán)函數(shù)逐幀繪制從當(dāng)前位置到目標(biāo)位置的運(yùn)動(dòng)效果筐骇。不同的是緩動(dòng)是指速度與距離成比例,而彈動(dòng)是加速度與距離成比例關(guān)系江滨。但是要模擬出更加真實(shí)的彈動(dòng)铛纬,可能需要加入類似摩擦力系數(shù)的因子,把速度逐漸降下唬滑,直到停止運(yùn)動(dòng)

一個(gè)簡(jiǎn)單的動(dòng)畫庫:

var box = document.getElementById('box');
        tween(box,{
            left:1000,
            top:500,
            opacity:0.2,
            width:50,
            height:50
        },2000,3,function(){
            utils.css(this,'background','aqua');
        })




~function(){

    var dc = {
        // 勻速:
        Linear:function(t,b,c,d){
            return c * t / d + b;
        },
        // 指數(shù)衰減的反彈緩動(dòng)
        Bounce: {
            easeIn: function (t, b, c, d) {
                return c - dc.Bounce.easeOut(d - t, 0, c, d) + b;
            },
            easeOut: function (t, b, c, d) {
                if ((t /= d) < (1 / 2.75)) {
                    return c * (7.5625 * t * t) + b;
                } else if (t < (2 / 2.75)) {
                    return c * (7.5625 * (t -= (1.5 / 2.75)) * t + .75) + b;
                } else if (t < (2.5 / 2.75)) {
                    return c * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375) + b;
                } else {
                    return c * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375) + b;
                }
            },
            easeInOut: function (t, b, c, d) {
                if (t < d / 2) {
                    return dc.Bounce.easeIn(t * 2, 0, c, d) * .5 + b;
                }
                return dc.Bounce.easeOut(t * 2 - d, 0, c, d) * .5 + c * .5 + b;
            }
        },
        // 二次方緩動(dòng)
        Quad: {
            easeIn: function (t, b, c, d) {
                return c * (t /= d) * t + b;
            },
            easeOut: function (t, b, c, d) {
                return -c * (t /= d) * (t - 2) + b;
            },
            easeInOut: function (t, b, c, d) {
                if ((t /= d / 2) < 1) {
                    return c / 2 * t * t + b;
                }
                return -c / 2 * ((--t) * (t - 2) - 1) + b;
            }
        },
        // 三次方緩動(dòng)
        Cubic: {
            easeIn: function (t, b, c, d) {
                return c * (t /= d) * t * t + b;
            },
            easeOut: function (t, b, c, d) {
                return c * ((t = t / d - 1) * t * t + 1) + b;
            },
            easeInOut: function (t, b, c, d) {
                if ((t /= d / 2) < 1) {
                    return c / 2 * t * t * t + b;
                }
                return c / 2 * ((t -= 2) * t * t + 2) + b;
            }
        },
        // 四次方緩動(dòng)
        Quart: {
            easeIn: function (t, b, c, d) {
                return c * (t /= d) * t * t * t + b;
            },
            easeOut: function (t, b, c, d) {
                return -c * ((t = t / d - 1) * t * t * t - 1) + b;
            },
            easeInOut: function (t, b, c, d) {
                if ((t /= d / 2) < 1) {
                    return c / 2 * t * t * t * t + b;
                }
                return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
            }
        },
        // 五次方緩動(dòng)
        Quint: {
            easeIn: function (t, b, c, d) {
                return c * (t /= d) * t * t * t * t + b;
            },
            easeOut: function (t, b, c, d) {
                return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
            },
            easeInOut: function (t, b, c, d) {
                if ((t /= d / 2) < 1) {
                    return c / 2 * t * t * t * t * t + b;
                }
                return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
            }
        },
        // 正弦曲線緩動(dòng)
        Sine: {
            easeIn: function (t, b, c, d) {
                return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
            },
            easeOut: function (t, b, c, d) {
                return c * Math.sin(t / d * (Math.PI / 2)) + b;
            },
            easeInOut: function (t, b, c, d) {
                return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
            }
        },
        // 指數(shù)曲線緩動(dòng)
        Expo: {
            easeIn: function (t, b, c, d) {
                return (t == 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b;
            },
            easeOut: function (t, b, c, d) {
                return (t == d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
            },
            easeInOut: function (t, b, c, d) {
                if (t == 0) return b;
                if (t == d) return b + c;
                if ((t /= d / 2) < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
                return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b;
            }
        },
        // 原圓形線緩動(dòng)
        Circ: {
            easeIn: function (t, b, c, d) {
                return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
            },
            easeOut: function (t, b, c, d) {
                return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
            },
            easeInOut: function (t, b, c, d) {
                if ((t /= d / 2) < 1) {
                    return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b;
                }
                return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
            }
        },
        //超出范圍的三次方緩動(dòng)
        Back: {
            easeIn: function (t, b, c, d, s) {
                if (s == undefined) s = 1.70158;
                return c * (t /= d) * t * ((s + 1) * t - s) + b;
            },
            easeOut: function (t, b, c, d, s) {
                if (s == undefined) s = 1.70158;
                return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
            },
            easeInOut: function (t, b, c, d, s) {
                if (s == undefined) s = 1.70158;
                if ((t /= d / 2) < 1) {
                    return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
                }
                return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
            }
        },
        //?指數(shù)衰減的正弦曲線緩動(dòng)
        Elastic: {
            easeIn: function (t, b, c, d, a, p) {
                if (t == 0) return b;
                if ((t /= d) == 1) return b + c;
                if (!p) p = d * .3;
                var s;
                !a || a < Math.abs(c) ? (a = c, s = p / 4) : s = p / (2 * Math.PI) * Math.asin(c / a);
                return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
            },
            easeOut: function (t, b, c, d, a, p) {
                if (t == 0) return b;
                if ((t /= d) == 1) return b + c;
                if (!p) p = d * .3;
                var s;
                !a || a < Math.abs(c) ? (a = c, s = p / 4) : s = p / (2 * Math.PI) * Math.asin(c / a);
                return (a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b);
            },
            easeInOut: function (t, b, c, d, a, p) {
                if (t == 0) return b;
                if ((t /= d / 2) == 2) return b + c;
                if (!p) p = d * (.3 * 1.5);
                var s;
                !a || a < Math.abs(c) ? (a = c, s = p / 4) : s = p / (2 * Math.PI) * Math.asin(c / a);
                if (t < 1) return -.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
                return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * .5 + c + b;
            }
        }
        
    }

    // 實(shí)現(xiàn)多方向的運(yùn)動(dòng)動(dòng)畫:操作元素告唆,目標(biāo)位置(是一個(gè)對(duì)象,存儲(chǔ)每一個(gè)方向的目標(biāo)位置)
    // effect:支持以下的情況
    // ["Linear","Circ.easeInOut","Elastic.easeOut","Back.easeOut","Expo.easeIn"]
    // 1晶密。如果傳遞進(jìn)來的是一個(gè)數(shù)字:0->Linear,1->Circ.easeInOut,2->Elastic.easeOut
    // 2.如果傳進(jìn)來的是一個(gè)數(shù)組:["Circ","easeInOut"] ->dc.Circ.easeInOut
    // 3.如果不傳遞的話擒悬,默認(rèn)使用Linear
    function move(curEle,target,duration,effect,callBack){

        //處理我們需要的動(dòng)畫效果
        var tempEffect = dc.Linear;
        if(typeof effect == 'number'){
            switch (effect) {
                case 0:
                    tempEffect = dc.Linear;
                    break;
                case 1:
                    tempEffect = dc.Bounce.easeIn;
                    break;
                case 2:
                    tempEffect = dc.Quart.easeInOut;
            }
        }else if(effect instanceof Array){
            tempEffect = effect.length >=2? dc[effect[0]][effect[1]] : dc[effect[0]];
        }else if(typeof effect == 'function'){ 
            callback = effect;
        }
        console.log(tempEffect);

        clearInterval(dc.timer);
        // 根據(jù)target獲取每一個(gè)方向的初始值begin和總距離change
        var begin = {};
        var change = {};
        for(var key in target){
            //key : top/left
            if(target.hasOwnProperty(key)){
                // top = 50  ,這個(gè)值是實(shí)時(shí)的值,會(huì)不斷變化
                // target[key]是個(gè)固定值,參數(shù)值
                begin[key] = utils.css(curEle,key);
                change[key] = target[key] - begin[key];
            }
        }
        // 實(shí)現(xiàn)多方向的運(yùn)動(dòng)動(dòng)畫
        var time = 0;
        dc.timer = setInterval(function(){
            time += 10;
            if(time > duration){
                utils.css(curEle,target);
                clearInterval(dc.timer);
                // 在動(dòng)畫結(jié)束的時(shí)候惹挟,傳遞回調(diào)函數(shù)
                // typeof callBack === "function" ? callBack() : null;
                callBack && callBack.call(curEle);
                return;
            }

            // 獲取當(dāng)前位置并設(shè)置樣式
            for(var key in target){
                if(target.hasOwnProperty(key)){
                    var curPos = tempEffect(time,begin[key],change[key],duration);
                    utils.css(curEle,key,curPos);
                }
            }
        },10);
    }
    window.tween = move;
}();

參考鏈接:

JavaScript動(dòng)畫詳解(一) —— 循環(huán)與事件監(jiān)聽

JavaScript動(dòng)畫詳解(二) —— 緩動(dòng)動(dòng)畫

JavaScript動(dòng)畫詳解(三) —— 彈動(dòng)動(dòng)畫

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末茄螃,一起剝皮案震驚了整個(gè)濱河市缝驳,隨后出現(xiàn)的幾起案子连锯,更是在濱河造成了極大的恐慌归苍,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件运怖,死亡現(xiàn)場(chǎng)離奇詭異拼弃,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)摇展,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門吻氧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人咏连,你說我怎么就攤上這事盯孙。” “怎么了祟滴?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵振惰,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我垄懂,道長(zhǎng)骑晶,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任草慧,我火速辦了婚禮桶蛔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘漫谷。我一直安慰自己仔雷,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布舔示。 她就那樣靜靜地躺著朽寞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪斩郎。 梳的紋絲不亂的頭發(fā)上脑融,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音缩宜,去河邊找鬼肘迎。 笑死,一個(gè)胖子當(dāng)著我的面吹牛锻煌,可吹牛的內(nèi)容都是我干的妓布。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼宋梧,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼匣沼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起捂龄,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤释涛,失蹤者是張志新(化名)和其女友劉穎加叁,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體唇撬,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡它匕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了窖认。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片豫柬。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖扑浸,靈堂內(nèi)的尸體忽然破棺而出烧给,到底是詐尸還是另有隱情,我是刑警寧澤喝噪,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布创夜,位于F島的核電站,受9級(jí)特大地震影響仙逻,放射性物質(zhì)發(fā)生泄漏驰吓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一系奉、第九天 我趴在偏房一處隱蔽的房頂上張望檬贰。 院中可真熱鬧,春花似錦缺亮、人聲如沸翁涤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽葵礼。三九已至,卻和暖如春并鸵,著一層夾襖步出監(jiān)牢的瞬間鸳粉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國打工园担, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留届谈,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓弯汰,卻偏偏與公主長(zhǎng)得像艰山,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子咏闪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 前言 又到了炎熱的7月曙搬,很久沒有更新技術(shù)文章了,原因是上月月底實(shí)習(xí)結(jié)束,從公司離職纵装。然后最近在弄自己的項(xiàng)目和考駕照...
    大力有話說閱讀 9,045評(píng)論 4 14
  • JS動(dòng)畫 動(dòng)畫的原理盒子自身的 offsetLeft + 步長(zhǎng)哗脖;封裝動(dòng)畫封裝勻速動(dòng)畫:Math.abs()絕對(duì)值 ...
    AnnQi閱讀 546評(píng)論 0 3
  • JavaScript 動(dòng)畫框架 框架封裝 相信大家在很多門戶網(wǎng)站上都可以看到動(dòng)畫的交互效果瀑踢,通過這些動(dòng)畫生動(dòng)地體現(xiàn)...
    蟬翅的空響閱讀 1,219評(píng)論 0 1
  • 這一篇是接著上一篇 翻譯。原文點(diǎn)擊才避。上一篇主要講了animate的基本原理橱夭。這一篇主要將幾種常見的delta。 進(jìn)...
    wpzero閱讀 464評(píng)論 0 10
  • 2016年11月24日 星期四 今天感恩節(jié)桑逝,感恩我們相遇在AAA 我們能夠完成任何我們想做的事情棘劣,但不是所有的事情...
    zkishi閱讀 741評(píng)論 0 0