最近在做一個(gè)H5動(dòng)畫(huà)的小項(xiàng)目樟插,由于項(xiàng)目周期充裕,前期主要是搭架構(gòu)竿刁,也沒(méi)用其他多余的動(dòng)畫(huà)庫(kù)黄锤。由于經(jīng)驗(yàn)不足,在搭架構(gòu)的過(guò)程中也遇到挺多坑食拜,比如webpack的配置問(wèn)題鸵熟、場(chǎng)景動(dòng)畫(huà)類的封裝等等。這次就記錄下如何異步控制動(dòng)畫(huà)的出現(xiàn)和消失负甸。
回顧ES6的Promise和ES7的async/await
在ES6出來(lái)之前和不用其他動(dòng)畫(huà)庫(kù)的情況下流强,很多人做簡(jiǎn)單動(dòng)畫(huà)基本都是用css3 animation + setTimeout + jQuery animate,但這樣卻面臨一個(gè)問(wèn)題:沒(méi)錯(cuò)呻待,回調(diào)地獄和嵌套層次太深打月,代碼可讀性大打折扣。
但現(xiàn)在有了ES6蚕捉、ES7和babel奏篙,我們可以大膽地用promise和async/await來(lái)做動(dòng)畫(huà)的異步執(zhí)行。
async函數(shù)是generator的一個(gè)語(yǔ)法糖迫淹,目前能被babel編譯秘通。它的優(yōu)點(diǎn)在于外部不需要手動(dòng)調(diào)用next方法,也不需要鏈?zhǔn)秸{(diào)用then方法敛熬,可以說(shuō)是優(yōu)于promise和generator肺稀。用法非常簡(jiǎn)單,來(lái)看一個(gè)簡(jiǎn)單例子:
const getUserData = url => (
new Promise((resolve, reject) => {
axios.get(url)
.then(({ data }) => resolve(data))
.catch(error => reject(error));
})
);
const getUserDataByAsync = async () => {
let _username;
try{
const { username } = await getUserData('https://github.com/username');
_username = username;
if(_username) {
/* do something */
}
}catch(err){
console.log(err);
}
};
getUserDataByAsync();
- 首先我們定義了一個(gè)getUserData函數(shù)应民,返回一個(gè)promise對(duì)象實(shí)例盹靴,用于抓取github上的用戶信息。axios的get方法返回的也是一個(gè)promise對(duì)象瑞妇,請(qǐng)求成功后會(huì)執(zhí)行第一層promise的resolve方法,并把請(qǐng)求到的數(shù)據(jù)傳出去梭冠。
- 接下來(lái)定義一個(gè)名為getUserDataByAsync的async函數(shù)(也可以這么聲明:
async function getUserDataByAsync(){}
)辕狰,用于處理請(qǐng)求成功后的操作。await關(guān)鍵字后調(diào)用getUserData控漠,而在此之后的代碼(在try之內(nèi)蔓倍,await之后)則會(huì)被阻塞悬钳,待請(qǐng)求成功后才會(huì)繼續(xù)執(zhí)行。await getUserData()的返回值則是getUserData函數(shù)里resolve后傳入的data偶翅。這里我們用try-catch來(lái)捕獲請(qǐng)求失敗后的異常信息默勾,catch打印出來(lái)的則是getUserData里請(qǐng)求失敗后reject傳入的error。 - 最后再調(diào)用getUserDataByAsync函數(shù)就行了聚谁。
我們用同樣的方法封裝一個(gè)動(dòng)畫(huà)延時(shí)器
- 首先我們先封裝動(dòng)畫(huà)方法母剥,這里為了操作節(jié)點(diǎn)方便一點(diǎn)用了jQuery
const animate = several => {
for (let effectName in several) {
let effect = `${effectName}`,
element_list = (several[effectName] instanceof Array) ? several[effectName] : [];
element_list.forEach(element => {
//為了防止display和animation沖突我們需要判斷元素是否被隱藏了
if ($(element).is(':hidden')) {
$(element).show();
setTimeout(() => {
$(element).addClass(`animated ${effect}`);
}, 10);
} else {
$(element).addClass(`animated ${effect}`);
}
});
}
};
需配合自己寫(xiě)的css3 animation或引入animate.css庫(kù)。
- 再封裝一個(gè)延時(shí)器
const delay = (timeout = 0) => (
new Promise(resolve => {
setTimeout(resolve, timeout);
})
);
- 之后就可以隨意控制動(dòng)畫(huà)的出現(xiàn)和消失了
const pageAnimationStart = async () => {
const _sceneWrap= $('#scene-wrap'),
_title = _scene_wrap.find('.title'),
_tree = _scene_wrap.find('.tree'),
_apples = _scene_wrap.find('.apple');
animate({
'scaleIn': [_sceneWrap]
});
await delay(1000);
animate({
'slideBounceInDown': [_tree]
});
await delay(1500);
animate({
'bounceIn': [_title, _apples]
});
};
window.addEventListener('load', function(){
pageAnimationStart();
}, false);
頁(yè)面加載完成后形导,場(chǎng)景開(kāi)始环疼,先是整個(gè)場(chǎng)景背景_sceneWrap放大進(jìn)入,1秒后大樹(shù)_tree由上到下掉入朵耕,再過(guò)1.5秒后標(biāo)題_title和蘋(píng)果_apples跳入炫隶。
大概就是這樣一個(gè)步奏,以異步執(zhí)行阎曹、同步寫(xiě)法的方式伪阶,看起來(lái)特別舒服,清晰地展示了每一步該做什么处嫌,避免了的嵌套栅贴、回調(diào)和滿屏的then方法。
這就是async/await在動(dòng)畫(huà)處理中的簡(jiǎn)單方法锰霜。其他應(yīng)用場(chǎng)景等我研究過(guò)后再記錄吧!