假設(shè)需要實現(xiàn)一個div 盒子的寬高減速運動變化過渡梦重,首先要思考的問題就是,現(xiàn)在div盒子的寬高是多少亮瓷?要變化到多少琴拧?
首先需要解決的問題,需要獲取用戶輸入對象的當(dāng)前css屬性值寺庄,其次JavaScript要實現(xiàn)減速運動艾蓝,運動變化到多少需要使用定時器來把運動拆分成不同時間段的具體變化,從而實現(xiàn)動畫的過渡斗塘。下面來詳細(xì)講解JavaScript如何實現(xiàn)減速運動赢织。
不同的瀏覽器獲取css屬性值的寫法不一樣,所以要書寫判斷馍盟。IE和Opera瀏覽器支持的寫法是obj.currentStyle[attr]于置,其他w3c支持的瀏覽器支持的寫法是getComputedStyle(obj,null)[attr]。
以下是封裝獲取div元素的css屬性值的代碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style>
#box{width: 200px; height: 300px; background:tan; position: absolute; left: 0;}
</style>
</head>
<body>
<button id="btn1">按鈕1</button>
<button id="btn2">按鈕2</button>
<div id="box"></div>
<script>
var btn1 = document.getElementById("btn1");
var btn2 = document.getElementById("btn2");
var box = document.getElementById("box");
//封裝獲取用戶輸入對象對應(yīng)css屬性值函數(shù)
function getStyle(obj,attr){
//如果obj存在currentStyle
if(obj.currentStyle){
//IE Opera瀏覽器
//返回對象屬性值
return obj.currentStyle[attr];
}else{
//w3c支持的瀏覽器
return getComputedStyle(obj,null)[attr];
}
}
//這里輸出盒子的高度300px
//console.log(getStyle(box,"height"));
</script>
</body>
</html>
現(xiàn)在已經(jīng)可以得到用戶輸入屬性對應(yīng)的css屬性值贞岭,下面來講解如何實現(xiàn)動畫八毯。
首先需要理解減速運動的公式搓侄,減速運動在于每次的運動的步長越來越小,需要在循環(huán)定時器內(nèi)計算每次運動的步長。
步長 = (目標(biāo)值 - 當(dāng)前值)/ 步數(shù)
在循環(huán)定時器內(nèi)部话速,當(dāng)前值是不斷獲取不斷變化的讶踪,并且越來越接近目標(biāo)值,而對象的css屬性值也需要不斷變化泊交。
對象的css屬性值 = 當(dāng)前值 + 步長
目標(biāo)值是用戶輸入的屬性最終的值乳讥,而用戶輸入的最終值可能是多個屬性,所以最終值應(yīng)該是一個json數(shù)據(jù)廓俭,多個屬性操作則需要對json數(shù)據(jù)進(jìn)行遍歷云石。當(dāng)前值則需要在定時器內(nèi)不斷的獲取。
實現(xiàn)代碼和詳細(xì)注釋:
<script>
var btn1 = document.getElementById("btn1");
var btn2 = document.getElementById("btn2");
var box = document.getElementById("box");
//封裝獲取用戶輸入對象對應(yīng)css屬性值函數(shù)
function animate(obj,json){
setInterval(function(){
//遍歷用戶輸入屬性json數(shù)據(jù)
for(k in json){
//k是用戶輸入屬性
//json[k]是對應(yīng)屬性的值
//console.log(json[k]);
//獲取對象當(dāng)前css屬性值研乒,獲取的是字符型汹忠,需要轉(zhuǎn)化成數(shù)字
var curStyle = parseInt(getStyle(obj,k));
//得到步長
//步長 = (最終值 - 當(dāng)前值)/ 步數(shù);
var step = (json[k] - curStyle) / 10;
//控制臺輸出能看到步長不斷減小
//console.log(step)
//對象的css屬性值 = 當(dāng)前值 + 步長雹熬;
obj.style[k] = curStyle + step + "px";
}
},10)
}
//點擊btn1能看到box寬高動畫變化
btn1.onclick = function(){
animate(box,{width:400,height:600});
}
</script>
如下圖所示宽菜,點擊btn1已經(jīng)可以實現(xiàn)盒子寬高的減速動畫變化過渡。
前面步驟雖然已經(jīng)可以實現(xiàn)盒子動畫減速過渡變化橄唬,但是審查元素發(fā)現(xiàn)盒子最終寬高并不等于用戶輸入的數(shù)值赋焕。如下圖所示:
原因是因為瀏覽器最小只能識別1px,但是步長step在除以步數(shù)的過程中仰楚,會出現(xiàn)除不盡的小數(shù)情況隆判。所以在這里步長要作一個取整處理。如果步長是正數(shù)則向上取整僧界,否則向下取整侨嘀。
Math.ceil()向上取整,取比該數(shù)值大的整數(shù)
Math.floor()向下取整捂襟,取比該數(shù)值小的整數(shù)
步長加上以下判斷賦值則可以解決這個問題咬腕。
step = step>0?Math.ceil(step):Math.floor(step);
上面已經(jīng)可以實現(xiàn)盒子寬高減速運動變化,那么什么時候停止定時器呢葬荷?
所有用戶輸入的css屬性都完成動畫變化過渡則可以停止定時器涨共。
在遍歷外聲明兩個變量來記錄屬性變化,聲明num記錄屬性總個數(shù),聲明key來記錄已經(jīng)完成變化屬性的個數(shù)宠漩。遍歷前兩個變量的值都先設(shè)置為0举反。
屬性總個數(shù)num遍歷一次增加一個num++,已經(jīng)完成變化屬性個數(shù)key扒吁,變化完成一個增加一個 key++火鼻,怎么判斷屬性是否變化完成?如果當(dāng)前值 = 目標(biāo)值,該屬性就變化完成魁索。
下面代碼把定時器賦給了對象obj的timer屬性融撞,如果需要調(diào)整運動速度可以通過修改定時器的間隔時間或者步數(shù)。
以下代碼包含詳細(xì)注釋:
<script>
var btn1 = document.getElementById("btn1");
var btn2 = document.getElementById("btn2");
var box = document.getElementById("box");
//封裝獲取用戶輸入對象對應(yīng)css屬性值函數(shù)
function getStyle(obj,attr){
//如果obj存在currentStyle
if(obj.currentStyle){
//IE Opera瀏覽器
//返回對象屬性值
return obj.currentStyle[attr];
}else{
//w3c支持的瀏覽器
return getComputedStyle(obj,null)[attr];
}
}
//這里輸出盒子的高度300px
//console.log(getStyle(box,"height"));
//封裝緩動動畫函數(shù)
function animate(obj,json){
//為了優(yōu)化效率粗蔚,開啟定時器前先清除定時器
clearInterval(obj.timer);
obj.timer = setInterval(function(){
//在遍歷外記錄屬性個數(shù)尝偎,初始都定義為0
var num = 0; //記錄總個數(shù)
var key = 0;//記錄已達(dá)到屬性的個數(shù)
//遍歷用戶輸入屬性json數(shù)據(jù)
for(k in json){
//k是用戶輸入屬性
//json[k]是對應(yīng)屬性的值
//console.log(json[k]);
//獲取對象當(dāng)前css屬性值,獲取的是字符型,需要轉(zhuǎn)化成數(shù)字
var curStyle = parseInt(getStyle(obj,k));
//得到步長
//步長 = (最終值 - 當(dāng)前值)/ 步數(shù)鹏控;
var step = (json[k] - curStyle) / 10;
//控制臺能看到步長不斷減小
//console.log(step)
//Math.ceil()向上取整冬念,取比該數(shù)值大的整數(shù)
//Math.floor()向下取整,取比該數(shù)值小的整數(shù)
//步長判斷取整后重新賦值
step = step>0?Math.ceil(step):Math.floor(step);
//對象的css屬性值 = 當(dāng)前值 + 步長牧挣;
obj.style[k] = curStyle + step + "px";
//屬性總個數(shù)遍歷一次增加一個
num++;
//如果當(dāng)前值等于用戶輸入的值,則已經(jīng)完成一個屬性的變化
if(curStyle == json[k]){
//key記錄的是已經(jīng)完成變化的屬性數(shù)醒陆,到達(dá)一個增加一個key++
key ++;
}
}
//在遍歷外面判斷到達(dá)屬性的個數(shù)是否等于遍歷屬性的總個數(shù)
if(key == num){
//如果兩個相等瀑构,表示所有屬性變化完成,則停止定時器
clearInterval(obj.timer)
}
//console.log(num)
//console.log(key)
},30)
}
//點擊btn1能看到box屬性動畫變化
btn1.onclick = function(){
animate(box,{width:1200,height:400,marginTop:100});
}
//點擊btn2能看到box屬性動畫變化
btn2.onclick = function(){
animate(box,{width:200,height:200,marginTop:0});
}
</script>
點擊按鈕效果刨摩,如下圖所示:
到這一步寺晌,已經(jīng)完成了減速運動變化框架封裝。
最后一步來進(jìn)行優(yōu)化澡刹,為減速運動框架添加回調(diào)函數(shù)呻征。
所謂回調(diào)函數(shù)是指在動畫運動完成后執(zhí)行的函數(shù)。需要給animate函數(shù)添加一個參數(shù)fn接收回調(diào)函數(shù)罢浇,如果用戶有傳回調(diào)函數(shù)陆赋,運動完成后就執(zhí)行這個函數(shù),所以是在停止定時器后再進(jìn)行判斷和調(diào)用fn嚷闭。
下面為減速運動框架封裝最終代碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style>
#box{width: 200px; height: 300px; background:tan; position: absolute; left: 0;}
</style>
</head>
<body>
<button id="btn1">按鈕1</button>
<button id="btn2">按鈕2</button>
<div id="box"></div>
<script>
var btn1 = document.getElementById("btn1");
var btn2 = document.getElementById("btn2");
var box = document.getElementById("box");
//封裝獲取用戶輸入對象對應(yīng)css屬性值函數(shù)
function getStyle(obj,attr){
//如果obj存在currentStyle
if(obj.currentStyle){
//IE Opera瀏覽器
//返回對象屬性值
return obj.currentStyle[attr];
}else{
//w3c支持的瀏覽器
return getComputedStyle(obj,null)[attr];
}
}
//這里輸出盒子的高度300px
//console.log(getStyle(box,"height"));
//封裝緩動動畫函數(shù)
function animate(obj,json,fn){
//為了優(yōu)化效率攒岛,開啟定時器前先清除定時器
clearInterval(obj.timer);
obj.timer = setInterval(function(){
//在遍歷外記錄屬性個數(shù),初始都定義為0
var num = 0; //記錄總個數(shù)
var key = 0;//記錄已達(dá)到屬性的個數(shù)
//遍歷用戶輸入屬性json數(shù)據(jù)
for(k in json){
//k是用戶輸入屬性
//json[k]是對應(yīng)屬性的值
//console.log(json[k]);
//獲取對象當(dāng)前css屬性值,獲取的是字符型胞锰,需要轉(zhuǎn)化成數(shù)字
var curStyle = parseInt(getStyle(obj,k));
//得到步長
//步長 = (最終值 - 當(dāng)前值)/ 步數(shù)灾锯;
var step = (json[k] - curStyle) / 10;
//控制臺能看到步長不斷減小
//console.log(step)
//Math.ceil()向上取整,取比該數(shù)值大的整數(shù)
//Math.floor()向下取整嗅榕,取比該數(shù)值小的整數(shù)
//步長判斷取整后重新賦值
step = step>0?Math.ceil(step):Math.floor(step);
//對象的css屬性值 = 當(dāng)前值 + 步長顺饮;
obj.style[k] = curStyle + step + "px";
//屬性總個數(shù)遍歷一次增加一個
num++;
//如果當(dāng)前值等于用戶輸入的值,則已經(jīng)完成一個屬性的變化
if(curStyle == json[k]){
//key記錄的是已經(jīng)完成變化的屬性數(shù)凌那,到達(dá)一個增加一個key++
key ++;
}
}
//在遍歷外面判斷到達(dá)屬性的個數(shù)是否等于遍歷屬性的總個數(shù)
if(key == num){
//如果兩個相等兼雄,表示所有屬性變化完成,則停止定時器
clearInterval(obj.timer);
//如果存在就調(diào)用
//if(fn){
//fn();
//}
//化簡寫法
fn&&fn();
}
//console.log(num)
//console.log(key)
},20)
}
//點擊btn1能看到box屬性動畫變化
btn1.onclick = function(){
animate(box,{width:1200,height:400,marginTop:100},function(){
alert("按鈕1運動完成案怯!")
});
}
//點擊btn2能看到box屬性動畫變化
btn2.onclick = function(){
animate(box,{width:200,height:200,marginTop:0});
}
</script>
</body>
</html>
注意這個減速運動框架只適用于屬性值為正整數(shù)的屬性君旦,屬性值為小數(shù)例如opacity和屬性值非數(shù)字的屬性暫不支持。