DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> JavaScript基礎知識 >> JavaScript學習總結【11】、JS運動
JavaScript學習總結【11】、JS運動
編輯:JavaScript基礎知識     

  動畫效果在網站中是一種非常常見的交互式體驗效果,比如側邊欄分享、圖片淡入淡出,我們把這種動畫效果就叫做運動,也就是讓物體動起來。如果想讓一個物體動起來,無非就是改變它的速度,也就是改變屬性值,比如 left、right、width、height、opacity ,那麼既然是運動,就可以分為很多種,如勻速運動、緩沖運動、多物體運動、任意值運動、鏈式運動和同時運動。我們從最簡單的動畫開始,如何讓單個物體運動,逐步深入多物體運動、多動畫同時運動到實現完美運動框架的封裝,在這個過程中,每一個運動都封裝為一個函數,可以更好的培養和鍛煉我們的編程思想,增強邏輯思維。

 

1、簡單運動

  簡單運動的實現都是勻速運動,顧名思義就是運動速度不變,通過寬、高、透明度等的變化,實現簡單的動畫效果,下面我們就讓一個 div 運動起來

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>簡單運動</title>
 <style>
 *{margin:0;padding:0;}
 #div1{
     width:200px;
     height:200px;
     background:green;
     position:absolute;
 }
 </style>
 <script>
 function startMove(){
     var oDiv = document.getElementById('div1');
     setInterval(function (){
         oDiv.style.left = oDiv.offsetLeft + 10 + 'px';
     },30);
 }
 </script>
 </head>
 <body>
 <input type="button" value="動起來" onclick="startMove()">
 <div id="div1"></div>
 </body>
 </html>

  讓一個 div 動起來,只需要開一個定時器,用於定義速度,告訴物體運動的快慢,上面的代碼,當點擊按鈕後,div 每隔 30毫秒 從左向右運動 10像素。

  這裡需要注意,讓 div 向右運動,在定義樣式時,一定要給運動的物體加絕對定位,也就是相對於哪個位置進行運動,offsetLeft 代表物體的當前位置,所以每次運動,都是給當前的 offsetLeft 加 10像素。

  雖然是讓 div 動起來了,但是問題多多,動起來後根本停不下來,這樣就太任性了,而且當重復點擊的話,運動速度還會加快,這都不是我們想要的,下面就我們就讓他停止在指定位置處

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>簡單運動</title>
 <style>
 *{margin:0;padding:0;}
 #div1{
     width:200px;
     height:200px;
     background:green;
     position:absolute;
 }
 </style>
 <script>
 var timer = null;
 function startMove(){
     var oDiv = document.getElementById('div1');
     clearInterval(timer);
     timer = setInterval(function (){
         if(oDiv.offsetLeft == 300){
             clearInterval(timer);
         }
         else{
             oDiv.style.left = oDiv.offsetLeft + 10 + 'px';
         }
     },30);
 }
 </script>
 </head>
 <body>
 <input type="button" value="動起來" onclick="startMove()">
 <div id="div1"></div>
 </body>
 </html>

  上面的代碼,點擊按鈕後,div 從左向右運動到 300像素 時停止運動。

  若要停止一個物體的運動,只要關閉定時器即可,也就是判斷 div 的 offsetLeft 值是否等於 300,若等於 300 則清空定時器。這裡需要注意,在做判斷時,一定要給運動位置加 else,判斷語句為二選一,成立或者不成立時執行,這樣再點擊按鈕就不會運動了,否則當 div 運動到 300像素 時,再點擊按鈕,div 還會向右移動 10像素,雖然在到達 300像素 時已經關閉了定時器,但是按鈕的點擊事件,還會執行一次函數,所以加了 else 之後,當再點擊按鈕時,這時候條件已經成立了,也就不會再執行 else 中的語句了。

  重復點擊按鈕,運動速度會不斷加快,是因為每點擊一次按鈕,startMove 函數被執行一次,多次點擊,也就相當於開了多個定時器,所以需要在執行 startMove 函數時,首先清空定時器,這樣重復點擊按鈕時,會先把之前運行的定時器關閉,再開一個新的定時器運行,這就保證了始終是一個定時器在工作。

  下面我們看兩個簡單動畫的實例:

  實例:側邊欄分享

  實現思路:網站側邊欄菜單是最常見的動畫效果,該效果在初始時,只顯示一個按鈕或者菜單項,當鼠標移上去時,滑出隱藏部分,展示內容。該效果在做布局時,主要用絕對定位實現隱藏,left 的值為內容容器 width 的值,該值為負值,動畫效果實現就是改變它的 offsetLeft 的值,當鼠標移入時,增加 offsetLeft 的值,值為 0 時停止運動,當鼠標移開時,offsetLeft 的值從 0 減小到 left 的值。

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>側邊欄分享</title>
 <style>
 *{margin:0;padding:0;}
 #div1{
     width:150px;
     height:210px;
     background:lightgreen;
     position:absolute;
     left:-150px;
 }
 #div1 span{
     width:20px;
     height:60px;
     line-height:20px;
     color:white;
     background:green;
     position:absolute;
     right:-20px;
     top:70px;
 }
 </style>
 <script>
 window.onload = function (){
     var oDiv = document.getElementById('div1');
     oDiv.onmouseover = function (){
         startMove();
     };
     oDiv.onmouseout = function (){
         stopMove();
     };
 };
 var timer = null;
 function startMove(){
     var oDiv = document.getElementById('div1');
     clearInterval(timer);
     timer = setInterval(function (){
         if(oDiv.offsetLeft == 0){
             clearInterval(timer);
         }
         else{
             oDiv.style.left = oDiv.offsetLeft + 10 + 'px';
         }
     },30);
 }
 function stopMove(){
     var oDiv = document.getElementById('div1');
     clearInterval(timer);
     timer = setInterval(function (){
         if(oDiv.offsetLeft == -150){
             clearInterval(timer);
         }
         else{
             oDiv.style.left = oDiv.offsetLeft - 10+ 'px';
         }
     },30);
 }
 </script>
 </head>
 <body>
 <div id="div1">
     <span>分享到</span>
 </div>
 </body>
 </html>

  上面的代碼,當鼠標移入"分享到",隱藏的 div 即內容容器每隔 30毫秒 從左向右運動 10像素,offsetLeft 值為 0 時停止運動,當鼠標移開時,顯示的 div 每隔 30毫秒 從右向左移動 5像素,offsetLeft 值為 -150 時停止運動。這裡要注意的是,offsetLeft 值的變化,從左向右運動時,值為正值,也就是 +10,從右向左運動時,值為負值,也就是 -10。

  我們可以看到,startMove 和 stopMove 都有相同的代碼結構,如果代碼中存在大致相同的代碼,就可以對代碼進行優化,上面的代碼,就只有 offsetLeft 的移動位置 和 停止位置不同,因此可以用函數傳參的方式將上面的代碼簡化為:

 <script>
 window.onload = function (){
     var oDiv = document.getElementById('div1');
     oDiv.onmouseover = function (){
         startMove(10, 0);
     };
     oDiv.onmouseout = function (){
         startMove(-10, -150);
     };
 };
 var timer = null;
 function startMove(speed, iTarget){
     var oDiv = document.getElementById('div1');
     clearInterval(timer);
     timer = setInterval(function (){
         if(oDiv.offsetLeft == iTarget){
             clearInterval(timer);
         }
         else{
             oDiv.style.left = oDiv.offsetLeft + speed + 'px';
         }
     },30);
 }
 </script>

  移動位置也就是速度,用 speed 參數傳入,停止位置也就是目標位置,用 iTarget 參數傳入。在功能相同的情況下,一個函數傳入的參數越少越好,那麼還可以簡化為:

 <script>
 window.onload = function (){
     var oDiv = document.getElementById('div1');
     oDiv.onmouseover = function (){
         startMove(0);
     };
     oDiv.onmouseout = function (){
         startMove(-150);
     };
 };
 var timer = null;
 function startMove(iTarget){
     var oDiv = document.getElementById('div1');
     clearInterval(timer);
     timer = setInterval(function (){
         var speed = 0;
         if(oDiv.offsetLeft > iTarget){
             speed = -10;
         }
         else{
             speed = 10;
         }
         if(oDiv.offsetLeft == iTarget){
             clearInterval(timer);
         }
         else{
             oDiv.style.left = oDiv.offsetLeft + speed + 'px';
         }
     },30);
 }
 </script>

  速度值和目標值,目標值肯定是不能省略的,就好比坐火車,買票肯定得有一個終點,所以速度值可以被省略掉,不管是動車還是普通車,都會到達終點,不同的就是速度的快慢。首先讓速度值等於 0,再做一個判斷,判斷目標值與物體當前位置的關系,如果 offsetLeft 值大於目標值,那麼就要從右向左運動,所以為負值,否則,也就是目標值大於 offsetLeft 值,這說明此時物體是隱藏的,那麼就從左向右運動,速度值為正值。

  該效果如果用緩沖運動做的話,效果更好。

  實例:淡入淡出

  淡入淡出效果是鼠標移入移出改變透明度,透明度動畫也屬於運動效果,可以使用上例中 startMove 框架完成這種效果。

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>淡入淡出</title>
 <style>
 #div1{
     width:200px;
     height:200px;
     background:red;
     filter:alpha(opacity:30);
     opacity:0.3;
 }
 </style>
 <script>
 window.onload = function (){
     var oDiv = document.getElementById('div1');
     oDiv.onmouseover = function (){
         startMove(100);
     };
     oDiv.onmouseout = function (){
         startMove(30);
     };
 };
 var alpha = 30;    //將透明度值存儲在變量中
 var timer = null;
 function startMove(iTarget){
     var oDiv = document.getElementById('div1');
     clearInterval(timer);
     timer = setInterval(function (){
         var speed = 0;
         if(alpha > iTarget){
             speed = -10;
         }
         else{
             speed = 10;
         }
         if(alpha == iTarget){
             clearInterval(timer);
         }
         else{
             alpha += speed;    //透明度值增加效果
             oDiv.style.opacity = alpha/100;    //高版本濾鏡為:0.1-1,所以除以100
             oDiv.style.filter = 'alpha(opacity:'+ alpha +')';    //IE低版本浏覽器使用
         }
     },30);
 }
 </script>
 </head>
 <body>
 <div id="div1"></div>
 </body>
 </html>

   改變透明度的值,可沒有 offsetalpha 這樣的寫法,透明度值沒有直接的屬性可以改變,但是可以將透明度值存儲在變量中,通過判斷變量的值和目標值之間的關系,就可以確定速度值,有了速度值之後,再將這個速度值通過變量賦值給他,就可以達到改變透明度值的效果。

  

2、緩沖運動

  勻速運動的速度始終都是不變的,而緩沖運動的速度則是逐漸變慢,最後停止的,就像火車一樣,出站後速度都是很快的,距離目標位置越近,速度會逐漸變慢,到站後停止,也就是緩沖運動的速度是由距離決定的,速度和距離成正比的,距離越遠,速度越大。

  那麼,緩沖運動的速度就可以用這個公式表示:速度 = (目標值 - 當前值)/縮放系數

  何為縮放系數,為什麼要除以縮放系數,我們看下面的實例。

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>緩沖運動</title>
 <style>
 #div1{
     width:100px;
     height:100px;
     background:green;
     position:absolute;
     left:0;
     top:50px;
 }
 #div2{
     width:1px;
     height:300px;
     background:black;
     position:absolute;
     left:300px;
     top:0;
 }
 </style>
 <script>
 function startMove(){
     var oDiv = document.getElementById('div1');
     setInterval(function (){
         var speed = 300 - oDiv.offsetLeft;
         oDiv.style.left = oDiv.offsetLeft + speed + "px";
     },30);
 }
 </script>
 </head>
 <body>
 <input type="button" value="動起來" onclick="startMove()">
 <div id="div1"></div>
 <div id="div2">300px處</div>
 </body>
 </html>

   上面的代碼,在點擊按鈕後,讓 div 運動到 300像素 處停止,可以看到 div 直接就跳到 300像素 處了,他的運動速度等於目標位置減去當前位置,初始位置為 0,所以點擊按鈕後,一瞬間就跳到 300像素 處了。

  速度太大了,就沒有緩沖的效果了,所以要除以縮放系數,就是讓他有一個緩沖的過程,我們給他除以 10,初始位置 0,點擊按鈕後,速度為 30,當在最後靠近 300像素 處,速度就為 0,距離越小,則速度就越小。

var speed = (300 - oDiv.offsetLeft)/10;

 

  這樣雖然是達到了緩沖的效果,但是可以很明顯的看到,div 並不是停在 300像素 處,還差那麼一點,打開調試工具可以看到,此時的 left 值為 296.4px,像素的最小單位是 px,我們平時寫代碼時,不可能這麼寫 100.5px 或者 200.9px,出現這樣的情況就是因為這句代碼:oDiv.style.left = oDiv.offsetLeft + speed + "px",也就是 div 的 left 值是當前值加速度值,所以 296 就是當前的 left 值,296 到 300 差 4,再除以縮放系數 10,就是 0.4,因此 div 的 left 值就為296.4px,這並不是我們想要的,速度不能為小數,解決方法也很簡單,那就是向上取整。

speed = Math.seil(speed);

 

  那麼,問題又來了,如果 div 的初始值是在 600像素 處,這時候就是從左向右運動,速度小於 0,也就是速度為負值,向上取整之後,離要運動到的 300px 處還差一點,這時候需要向下取整,但是向下取整之後,初始值為為 0 時,運動到 300px 處還是差一點,這時候就需要做判斷,當速度大於 0 時,速度為正,向上取整,如果小於 0 時,速度為負,則向下取整。

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>緩沖運動</title>
 <style>
 #div1{
     width:100px;
     height:100px;
     background:green;
     position:absolute;
     left:0;
     top:50px;
 }
 #div2{
     width:1px;
     height:300px;
     background:black;
     position:absolute;
     left:300px;
     top:0;
 }
 </style>
 <script>
 function startMove(){
     var oDiv = document.getElementById('div1');
     setInterval(function (){
         var speed = (300 - oDiv.offsetLeft)/10;
         speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
         oDiv.style.left = oDiv.offsetLeft + speed + "px";
     },30);
 }
 </script>
 </head>
 <body>
 <input type="button" value="動起來" onclick="startMove()">
 <div id="div1"></div>
 <div id="div2">300px處</div>
 </body>
 </html>

  

  這裡一定要注意,緩沖運動一定要取整,否則就到不了目標位置。下面是一個用緩沖運動做的側邊欄分享效果

 

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>緩沖分享</title>
 <style>
 *{margin:0;padding:0;}
 #div1{
     width:150px;
     height:210px;
     background:lightgreen;
     position:absolute;
     left:-150px;
 }
 #div1 span{
     width:20px;
     height:60px;
     line-height:20px;
     color:white;
     background:green;
     position:absolute;
     right:-20px;
     top:70px;
 }
 </style>
 <script>
 window.onload = function (){
     var oDiv = document.getElementById('div1');
     oDiv.onmouseover = function (){
         startMove(0);
     };
     oDiv.onmouseout = function (){
         startMove(-150);
     };
 };
 var timer = null;
 function startMove(iTarget){
     var oDiv = document.getElementById('div1');
     clearInterval(timer);
     timer = setInterval(function (){
         var speed = (iTarget - oDiv.offsetLeft)/10;
         speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
         if(oDiv.offsetLeft == iTarget){
             clearInterval(timer);
         }
         else{
             oDiv.style.left = oDiv.offsetLeft + speed + 'px';
         }
     },30);
 }
 </script>
 </head>
 <body>
 <div id="div1">
     <span>分享到</span>
 </div>
 </body>
 </html>

 

  緩沖運動的停止條件是當兩點重合時,也就是物體當前的 left 值等於目標值,而勻速運動的停止條件是距離足夠近時,也就是物體當前的 left 值大於或小於目標值,再強行讓他的 left 值等於目標值,下面我們通過一個實例來更好的理解。

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>運動的停止</title>
 <style>
 #div1{
     width:100px;
     height:100px;
     background:green;
     position:absolute;
     left:600px;
     top:50px;
 }
 #div2{
     width:1px;
     height:300px;
     background:black;
     position:absolute;
     left:200px;
     top:0;
 }
 #div3{
     width:1px;
     height:300px;
     background:black;
     position:absolute;
     left:400px;
     top:0;
 }
 </style>
 <script>
 var timer = null;
 function startMove(iTarget){
     var oDiv = document.getElementById('div1');
     clearInterval(timer);
     timer = setInterval(function (){
         var speed = 0;
         if(oDiv.offsetLeft > iTarget){
             speed = -7;
         }
         else{
             speed = 7;
         }
         if(Math.abs(iTarget - oDiv.offsetLeft) <= 7){
             clearInterval(timer);
             oDiv.style.left = iTarget + 'px';
         }
         else{
             oDiv.style.left = oDiv.offsetLeft + speed + "px";
         }
     },30);
 }
 </script>
 </head>
 <body>
 <input type="button" value="到200" onclick="startMove(200)">
 <input type="button" value="到400" onclick="startMove(400)">
 <div id="div1"></div>
 <div id="div2">200px處</div>
 <div id="div3">400px處</div>
 </body>
 </html>

  上面的代碼,點擊 "到200" 按鈕,div 從右向左每隔 30毫秒 運動 7像素,速度為負,運動到 200像素 處停止,再點擊 "到400" 按鈕,div 從左向右每隔 30毫秒 運動 7像素,速度為正,運動到 400像素 處停止。勻速運動的速度始終是保持不變的,這裡的運動速度為 7,初始位置運動到目標位置的移動速度除不盡,這樣就會出現左右徘徊的情況,也就是 div 在目標點左右閃動,原因是前進一個 7,比目標位置大了,後退一個 7,比目標位置小了,所有會出現這種現象。解決辦法就是使用絕對值進行判斷,因為他的距離可能是正7,也可能是負7。那麼使用絕對值做判斷,目標位置減去物體當前位置若小於等於 7,就算到了,這時候問題就來了,雖然到了不閃動了,但是可以很明顯看到還有一點距離,所以最關鍵的一步,就是在清空定時器後,直接讓 div 的 left 值等於目標點的值,這樣就完成了勻速運動的停止。

  

3、多物體運動和任意值運動

  (1)、多物體運動

  我們之前做的都是單個物體的運動,而在網站中,並不是單個物體在運動,而是多個物體的運動,下面我們就套用之前的 startMove 框架讓多個 div 運動起來

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>多個div變寬</title>
 <style>
     div{
         width:100px;
         height:50px;
         background:green;
         margin:10px;
     }
 </style>
 <script>
 window.onload = function (){
     var aDiv = document.getElementsByTagName('div');
     for(var i=0; i<aDiv.length; i++){
         aDiv[i].onmouseover = function (){
             startMove(this,400);
         };
         aDiv[i].onmouseout = function (){
             startMove(this,100);
         };
     }
 };
 var timer = null;
 function startMove(obj,iTarget){
     clearInterval(timer);
     timer = setInterval(function (){
         var speed=(iTarget - obj.offsetWidth)/6;
         speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
 
         if(obj.offsetWidth == iTarget){
             clearInterval(timer);
         }
         else{
             obj.style.width = obj.offsetWidth + speed + 'px';
         }
     },30);
 }
 </script>
 </head>
 <body>
 <div></div>
 <div></div>
 <div></div>
 </body>
 </html>

  上面的代碼,有 3 個 div,在鼠標移入時寬從 100像素 運動到 400像素,而當鼠標移出後從 400像素 運動回 100像素,因為是多個物體的運動,因此要使用 for 循環讓每個物體都獲得事件,那麼要確定當前獲得事件的物體,就需要再傳入一個參數 this,我們不能確定當前是哪個物體獲得事件,所以使用 this 指向,但是我們之前寫的 startMove 框架只有一個參數,而多物體的運動傳入了2個參數,因為要確定當前運動的物體,所以只要再給 startMove 傳入一個參數 obj 就好了。那麼多物體的運動框架,就是再傳入一個參數,這樣就能獲取當前運動的物體了。

  這樣就算做完了嗎?肯定沒有,當鼠標同時滑過 3 個 div 時,就出事了,會發現變寬之後變不回來了,這是因為定時器,3 個 div 只用一個定時器,雖然在剛開始運行時,我們就清空了定時器,但只是清空了整個定時器,不知道清空的是誰的,這樣就造成了混亂,肯定就出錯了,所以要給每個 div 都設置一個定時器,我們知道可以給對象添加一個自定義屬性設置索引號 aDiv[i].index = 0,那麼就可以把定時器也作為物體的屬性 aDiv[i].timer = null 。

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>多個div變寬</title>
 <style>
     div{
         width:100px;
         height:50px;
         background:green;
         margin:10px;
     }
 </style>
 <script>
 window.onload = function (){
     var aDiv = document.getElementsByTagName('div');
     for(var i=0; i<aDiv.length; i++){
         aDiv[i].timer = null;
         aDiv[i].onmouseover = function (){
             startMove(this,400);
         };
         aDiv[i].onmouseout = function (){
             startMove(this,100);
         };
     }
 };
 function startMove(obj,iTarget){
     clearInterval(obj.timer);
     obj.timer = setInterval(function (){
         var speed=(iTarget - obj.offsetWidth)/6;
         speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
 
         if(obj.offsetWidth == iTarget){
             clearInterval(obj.timer);
         }
         else{
             obj.style.width = obj.offsetWidth + speed + 'px';
         }
     },30);
 }
 </script>
 </head>
 <body>
 <div></div>
 <div></div>
 <div></div>
 </body>
 </html>

 

  下面再看一個多個 div 淡入淡出的實例。

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>多個div淡入淡出</title>
 <style>
 div{
     width:200px;
     height:200px;
     background:red;
     float:left;
     margin:20px;
     filter:alpha(opacity:30);
     opacity:0.3;
 }
 </style>
 <script>
 window.onload = function (){
     var aDiv = document.getElementsByTagName('div');
     for(var i=0; i<aDiv.length; i++){
         aDiv[i].onmouseover = function (){
             startMove(this,100);
         };
         aDiv[i].onmouseout = function (){
             startMove(this,30);
         };
     }
 };
 var alpha = 30;
 function startMove(obj,iTarget){
     clearInterval(obj.timer);
     obj.timer = setInterval(function (){
         var speed=(iTarget - alpha)/6;
         speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
 
         if(alpha == iTarget){
             clearInterval(obj.timer);
         }
         else{
             alpha += speed;
             obj.style.opacity = alpha/100;
             obj.style.filter = 'alpha(opacity: '+ alpha +')';
         }
     },30);
 }
 </script>
 </head>
 <body>
 <div></div>
 <div></div>
 <div></div>
 <div></div>
 </body>
 </html>

  上面的代碼,4 個 div 初始的透明度都為 0.3,在鼠標移入後透明度變為 1,當鼠標移出後變回 0.3,單個移入移出都沒有問題,但是當快速移動鼠標時,又出事了,並沒有達到我們預期的效果,我們給當前的物體都設置了定時器,但為什麼還會出現這種情況呢?其實這並不是定時器的問題,而是用於存儲透明度的變量 alpha 導致的,4 個 div 公用了一個濾鏡,就導致了混亂,從這我們可以看出,凡是多物體運動,所有東西都不能公用,也就是要改變的屬性,必須給每個物體設置,要把屬性和運動東西綁定。

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>多個div淡入淡出</title>
 <style>
 div{
     width:200px;
     height:200px;
     background:red;
     float:left;
     margin:20px;
     filter:alpha(opacity:30);
     opacity:0.3;
 }
 </style>
 <script>
 window.onload = function (){
     var aDiv = document.getElementsByTagName('div');
     for(var i=0; i<aDiv.length; i++){
         aDiv[i].alpha = 30;
         aDiv[i].onmouseover = function (){
             startMove(this,100);
         };
         aDiv[i].onmouseout = function (){
             startMove(this,30);
         };
     }
 };
 function startMove(obj,iTarget){
     clearInterval(obj.timer);
     obj.timer = setInterval(function (){
         var speed=(iTarget - obj.alpha)/6;
         speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
 
         if(obj.alpha == iTarget){
             clearInterval(obj.timer);
         }
         else{
             obj.alpha += speed;
             obj.style.opacity = obj.alpha/100;
             obj.style.filter = 'alpha(opacity: '+ obj.alpha +')';
         }
     },30);
 }
 </script>
 </head>
 <body>
 <div></div>
 <div></div>
 <div></div>
 <div></div>
 </body>
 </html>

 

  (2)、任意值運動

  之前我們都做的是物體的單一運動,都是改變寬度或者改變透明度,在網站中,也肯定不只是單一的運動,有可能是變寬,也有可能是變高,下面我們就看一下 div 的變寬和變高

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>div變寬和變高</title>
 <style>
 div{
     width:100px;
     height:50px;
     background:green;
     margin-bottom:10px;
 }
 </style>
 <script>
 window.onload = function (){
     var oDiv1 = document.getElementById('div1');
     var oDiv2 = document.getElementById('div2');
 
     oDiv1.onmouseover = function (){
         startMove(this,400);
     };
     oDiv1.onmouseout = function (){
         startMove(this,100);
     };
 
     oDiv2.onmouseover = function (){
         startMove2(this,400);
     };
     oDiv2.onmouseout = function (){
         startMove2(this,50);
     };
 };
 function startMove(obj,iTarget){
     clearInterval(obj.timer);
     obj.timer = setInterval(function (){
         var speed = (iTarget - obj.offsetWidth)/6;
         speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
         if(obj.offsetWidth == iTarget){
             clearInterval(obj.timer);
         }
         else{
             obj.style.width = obj.offsetWidth + speed + 'px';
         }
     },30);
 }
 function startMove2(obj,iTarget){
     clearInterval(obj.timer);
     obj.timer = setInterval(function (){
         var speed = (iTarget - obj.offsetHeight)/6;
         speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
         if(obj.offsetHeight == iTarget){
             clearInterval(obj.timer);
         }
         else{
             obj.style.height = obj.offsetHeight + speed + 'px';
         }
     },30);
 }
 </script>
 </head>
 <body>
 <div id="div1">變寬</div>
 <div id="div2">變高</div>
 </body>
 </html>

   上面的代碼,div1 在鼠標移入時寬度從 100像素 運動到 400像素,移出後運動回 100像素,div2 在鼠標移入時高度從 50像素 運動到 400像素,移出後運動回 50像素,我們只要調用 2 個 startMove 框架就可以很輕松的完成這樣的效果,但是代碼顯得十分繁瑣,而且這 2 個框架的功能是完全相同的,唯一不同的就是一個改變寬度,一個改變高度,之前我們說過,對於功能完全相同的代碼,就可以對代碼進行簡化,把不同的東西作為參數傳入,這樣就可以得到一個任意值的運動框架。

  在這之前,我們先來看一下 offset 這個屬性前邊我們在做運動時,只給物體進行了簡單的樣式定義,寬、高和背景,但在實際的開發中,或許還有其他的樣式屬性,比如內外邊距或者邊框等,那如果在定義一個邊框屬性,會如何呢?看下面的實例:

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>offset屬性</title>
 <style>
 #div1{
     width:200px;
     height:200px;
     background:red;
     border:1px solid black;
 }
 </style>
 <script>
 setInterval(function (){
     var oDiv = document.getElementById('div1');
     oDiv.style.width = oDiv.offsetWidth - 1 + 'px';
 },30);
 </script>
 </head>
 <body>
 <div id="div1">變窄</div>
 </body>
 </html>

  上面的代碼,我們給 div 加了 1像素 的邊框,並且讓他每隔 30毫秒 從右向左運動 1像素,速度為負,也就是 offsetWidth - 1,本應該打開 demo 之後,會看到 div 會逐漸變窄,但是事與願違,div 並沒有逐漸變窄,反而逐漸變寬了,如果不加邊框的話,div 肯定會逐漸變窄,這就是 offset 屬性的一個小 bug在設置了邊框後,當前的寬度就要加上邊框的寬度,實際上 offsetWidth 的值就為 202,計算後為 201 並賦值給 width,當下一次運行時 width 的值就為 201,再加上邊框值 offsetWidth 的值則為 203,計算後為 202 並賦值給 width,依次類推,所以就會出現逐漸變寬的現象。

  要解決這個 bug,就是不使用 offset 屬性,最直接的辦法就是將 width 屬性放在行間,style.width 值只考慮本身的 width,而不考慮邊框或者內邊距及其他的影響因素,但是 style 僅僅只能取行間樣式,如果是寫在內部樣式表或外部樣式表,就沒法用了,並且這也不符合 W3C 結構和表現相分離的原則,所以該方法是不可取的,但是我們可以看以下他是怎麼實現的。

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>offset的bug</title>
 <style>
 #div1{
     height:200px;
     background:red;
     border:1px solid black;
 }
 </style>
 <script>
 setInterval(function (){
     var oDiv = document.getElementById('div1');
     oDiv.style.width = parseInt(oDiv.style.width) - 1 + 'px';
 },30);
 </script>
 </head>
 <body>
 <div id="div1" style="width:200px">變窄</div>
 </body>
 </html>

  上面的代碼,我們將 div 的 width 屬性寫在了行間,通過 oDiv.style.width 獲取,再使用 parseInt 函數將字符串轉換為一個整數,再減去速度,這樣問題就解決了。

  最佳的解決方法,就是獲取非行間樣式,我們可以封裝一個 getStyle 函數,用於獲取非行間樣式,再使用時傳入相應的參數。

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>offset的bug</title>
 <style>
 #div1{
     width:200px;
     height:200px;
     background:red;
     border:1px solid black;
 }
 </style>
 <script>
 function getStyle(obj,name){
     if(obj.currentStyle){
         return obj.currentStyle[name];
     }
     else{
         return getComputedStyle(obj,false)[name];
     }
 }
 setInterval(function (){
     var oDiv = document.getElementById('div1');
     oDiv.style.width = parseInt(getStyle(oDiv,'width')) - 1 + 'px';
 },30);
 </script>
 </head>
 <body>
 <div id="div1">變窄</div>
 </body>
 </html>

   getStyle 函數有 2 個參數,第一個參數 obj 為要獲取的對象,第二個參數 name 為要獲取的屬性,並且做了兼容處理,currentStyle 針對 IE 浏覽器,getComputedStyle 針對火狐浏覽器。

  既然 offset 這個屬性存在 bug,那麼再做動畫效果時就不能使用該屬性了,使用獲取非行間樣式的方法來做,下面我們就用這個方法來做 div 的變寬和變高

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>div變寬和變高</title>
 <style>
 div{
     width:100px;
     height:50px;
     background:green;
     margin-bottom:10px;
     border:5px solid black;
 }
 </style>
 <script>
 window.onload = function (){
     var oDiv = document.getElementById('div1');
     var oDiv2 = document.getElementById('div2');
     oDiv.onmouseover = function (){
         startMove(this,400);
     };
     oDiv.onmouseout = function (){
         startMove(this,100);
     };
 
     oDiv2.onmouseover = function (){
         startMove2(this,400);
     };
     oDiv2.onmouseout = function (){
         startMove2(this,50);
     };
 };
 function getStyle(obj,name){
     if(obj.currentStyle){
         return obj.currentStyle[name];
     }
     else{
         return getComputedStyle(obj,false)[name];
     }
 }
 function startMove(obj,iTarget){
     clearInterval(obj.timer);
     obj.timer = setInterval(function (){
         var cur = parseInt(getStyle(obj,'width'));
         var speed = (iTarget - cur)/6;
         speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
         if(cur == iTarget){
             clearInterval(obj.timer);
         }
         else{
             obj.style['width'] = cur + speed + 'px';
         }
     },30);
 }
 function startMove2(obj,iTarget){
     clearInterval(obj.timer);
     obj.timer = setInterval(function (){
         var cur = parseInt(getStyle(obj,'height'));
         var speed = (iTarget - cur)/6;
         speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
         if(cur == iTarget){
             clearInterval(obj.timer);
         }
         else{
             obj.style['height'] = cur + speed + 'px';
         }
     },30);
 }
 </script>
 </head>
 <body>
 <div id="div1">變寬</div>
 <div id="div2">變高</div>
 </body>
 </html>

   上面的代碼,我們給 div 設置了 5像素 的邊框,鼠標移入移出變寬和變高都是沒問題的,為了代碼的簡潔,我們將獲取物體當前屬性值的代碼保存在一個變量中,var cur = parseInt(getStyle(obj,'width')),方便之後調用,我們知道 obj.style.width 也可以寫為 obj.style['width'],只是後者寫起來比較麻煩,平時大家不這樣寫,此處這樣寫,可以更好的說明問題,仔細觀察代碼,原本的框架只能讓某個值運動起來,如果想要其他值運動起來,就必須修改程序,2 個 startMove 框架結構是完全相同的,只有傳入的屬性值不同,那麼我們就可以進行代碼優化了,將屬性值作為參數傳入  startMove 框架中,在使用時,傳入什麼屬性,就可以改變什麼屬性,這就是任意值運動框架。

  下面我們就檢測以下我們的任意值運動框架,做一個濾鏡效果,div 的淡入淡出

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>div淡入淡出</title>
 <style>
 div{
     width:200px;
     height:200px;
     background:red;
     border:1px solid black;
     filter:alpha(opacity:30);
     opacity:0.3;
 }
 </style>
 <script>
 window.onload = function (){
     var oDiv = document.getElementById('div1');
     oDiv.onmouseover = function (){
         startMove(this,'opacity',100);
     };
     oDiv.onmouseout = function (){
         startMove(this,'opacity',30);
     };
 };
 function getStyle(obj,name){
     if(obj.currentStyle){
         return obj.currentStyle[name];
     }
     else{
         return getComputedStyle(obj,false)[name];
     }
 }
 function startMove(obj,attr,iTarget){
     clearInterval(obj.timer);
     obj.timer = setInterval(function (){
         var cur = parseFloat(getStyle(obj,attr))*100;
         var speed = (iTarget - cur)/10;
         speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
         if(cur == iTarget){
             clearInterval(obj.timer);
         }
         else{
             obj.style.opacity = (cur+speed)/100;
             obj.style.filter = "alpha(opacity: '+ (cur + speed) +')";
             document.getElementById('txt1').value = obj.style.opacity;
         }
     },30);
 }
 </script>
 </head>
 <body>
 <input type="text" id="txt1">
 <div id="div1"></div>
 </body>
 </html>

  上面的代碼,div 初始透明度為 0.3,鼠標移入時變為 1,鼠標移開後恢復,還定義了一個文本框,用於顯示 div 的透明度值,這裡需要注意,透明度的值為小數,所以要使用 parseFloat 函數,將字符串轉換為浮點數,而不是使用 parseInt 函數,他用於將字符串轉換為整數,最後為了便於計算,在給這個數值乘以 100。注意觀察文本框顯示的屬性值,在鼠標移入時,顯示為 1.00999等 一長串數字,而當鼠標移開後,透明度的值一直在 0.300001 到 0.29000001 之間徘徊,怎麼會出現這情況呢?那是因為計算機存儲的小數是一個近似值,所以會有誤差,看下面的實例。

 <script>
 alert(0.03*100);    //返回:3
 alert(0.05*100);    //返回:5
 alert(10/3);        //返回:3.33……35
 alert(0.07*100);    //返回:7.00……01
 </script>

  上面的代碼,計算 0.03*100 和 0.05*100,結果為 3 和 5,計算結果正確,而計算 10/3 小數點後最後一位為 5,更離譜的是計算 0.07*100,結果居然不是7,這誤差也太大了,因此不建議進行小數點計算,所以解決上面問題的方法,就是取浮點數之後再進行四捨五入。

  那麼再觀察上面改變透明度的代碼,跟之前改變寬和高的代碼,雖然使用的框架是相同的,但是結構又是不相同的,也就是不能使用改變寬高的代碼來進行改變透明度,那這豈不是任意值的運動框架了,要怎麼辦呢?很簡單,對透明度特殊對待,也就是使用判斷,如果要改變的屬性為透明度,那麼就使用改變透明度的方法,否則就是改變其他屬性,那就使用改變寬高的方法。

  下面我們看一下任意值運動的綜合實例。

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>任意值運動框架</title>
 <style>
 #div1{
     filter:alpha(opacity:30);
     opacity:0.3;
 }
 div{
     width:100px;
     height:50px;
     background:red;
     margin-bottom:10px;
     border:1px solid black;
     font-size:14px;
 }
 </style>
 <script>
 window.onload = function (){
     var oDiv = document.getElementById('div1');
     var oDiv2 = document.getElementById('div2');
     var oDiv3 = document.getElementById('div3');
     var oDiv4 = document.getElementById('div4');
     oDiv.onmouseover = function (){
         startMove(this,'opacity',100);
     };
     oDiv.onmouseout = function (){
         startMove(this,'opacity',30);
     };
     oDiv2.onmouseover=function (){
         startMove(this,'fontSize',24);
     };
     oDiv2.onmouseout=function (){
         startMove(this,'fontSize',12);
     };
     oDiv3.onmouseover = function (){
         startMove(this,'width',400);
     };
     oDiv3.onmouseout = function (){
         startMove(this,'width',100);
     };
     oDiv4.onmouseover = function (){
         startMove(this,'height',400);
     };
     oDiv4.onmouseout = function (){
         startMove(this,'height',50);
     };
 };
 function getStyle(obj,name){
     if(obj.currentStyle){
         return obj.currentStyle[name];
     }
     else{
         return getComputedStyle(obj,false)[name];
     }
 }
 function startMove(obj,attr,iTarget){
     clearInterval(obj.timer);
     obj.timer = setInterval(function (){
         var cur = 0;    //用於存儲物體當前的屬性,方便之後調用
         if(attr == 'opacity'){
             cur = Math.round(parseFloat(getStyle(obj,attr))*100);
         }
         else{
             cur = parseInt(getStyle(obj,attr));
         }
         var speed = (iTarget - cur)/10;
         speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
         if(cur == iTarget){
            clearInterval(obj.timer);
         }
         else{
             if(attr == 'opacity'){
                 obj.style.opacity = (cur+speed)/100;
                 obj.style.filter = "alpha(opacity: '+ (cur + speed) +')";
             }
             else{
                 obj.style[attr] = cur + speed + "px";
             }
         }
     },30);
 }
 </script>
 </head>
 <body>
 <div id="div1">改變透明度</div>
 <div id="div2">改變文字大小</div>
 <div id="div3">改變寬度</div>
 <div id="div4">改變高度</div>
 </body>
 </html>

  上面的代碼,不只改變了 div 的透明度,也改變了寬度和高高度,還改變了字體的大小,當然也可以使用該框架改變他的邊框等其他屬性。

  

4、鏈式運動

  所謂鏈式運動,就像鏈條一樣,一個扣一個,當一個運動結束後,開始下一個運動,要實現這樣的效果,只需要放一個回調函數,也就是在運動停止時,執行的函數,再調用一次 startMove 框架,開始下一次運動,那這樣就好辦了,既然還是使用使用之前的框架,那麼就再給他傳入一個參數,這樣就可以完成鏈式運動,下面我們就來看一下在代碼中具體是怎麼實現的。

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>鏈式運動框架</title>
 <style>
 div{
     width:100px;
     height:100px;
     background:red;
     filter:alpha(opacity:30);
     opacity:0.3;
 }
 </style>
 <script>
 window.onload = function (){
     var oDiv = document.getElementById('div1');
     oDiv.onmouseover = function (){
         startMove(oDiv,'width',300,function(){
             startMove(oDiv,'height',300,function (){
                 startMove(oDiv,'opacity',100)
             });
         });
     };
     oDiv.onmouseout = function (){
         startMove(oDiv,'opacity',30,function (){
             startMove(oDiv,'height',100,function (){
                 startMove(oDiv,'width',100);
             });
         });
     };
 };
 function getStyle(obj,name){
     if(obj.currentStyle){
         return obj.currentStyle[name];
     }
     else{
         return getComputedStyle(obj,false)[name];
     }
 }
 function startMove(obj,attr,iTarget,fn){
     clearInterval(obj.timer);
     obj.timer = setInterval(function (){
         var cur = 0;
         if(attr == 'opacity'){
             cur = Math.round(parseFloat(getStyle(obj,attr))*100);
         }
         else{
             cur = parseInt(getStyle(obj,attr));
         }
         var speed = (iTarget - cur)/10;
         speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
         if(cur == iTarget){
             clearInterval(obj.timer);
             if(fn)fn();
         }
         else{
             if(attr == 'opacity'){
                 obj.style.opacity = (cur+speed)/100;
                 obj.style.filter = "alpha(opacity: '+ (cur + speed) +')";
             }
             else{
                 obj.style[attr] = cur + speed + "px";
             }
         }
     },30);
 }
 </script>
 </head>
 <body>
 <div id="div1"></div>
 </body>
 </html>

  上面的代碼,div 的初始透明度為 0.3,寬100px 高100px,鼠標移入後,div 的寬度先從 100px 運動到 300px 處停止,然後高度再從 100px 運動到 300px 處停止,最後透明度從 0.3 變為 1,當鼠標移開後,以相反的順序運動,即 div 的透明度先從 1 變回 0.3,然後高度從 300px 運動回 100px,最後寬度從300px 運動回 100px,這就是一個簡易的鏈式運動。

  觀察上面的代碼,我們要完成鏈式運動,那麼當一次運動結束後,就需要再執行一次 startMove 框架,那麼再傳入一個 fn 參數,fn 就是一個函數,當執行完一段代碼之後,沒有馬上結束,而是再調用一次這個函數,再執行一個新的程序。這裡最需要注意的是,當傳入一個參數進來後,需要檢測運動停止,在運動結束時清空定時器後,就要判斷一下,if(fn)fn(),只有當這個函數傳進來再調用,也就是第一次運動停止後,如果有這個 fn,也就是傳入了一個 fn,那麼就讓這個 fn 執行一次,如果沒有最後一個參數,那麼就不需要再往下執行,運動就停止了。

  

5、同時運動

  我們剛封裝了鏈式運動框架,就是一個扣一個,div 先變寬完成之後再變高,最後再改變透明度,這些都是單一的運動效果,即每一次運動都只能改變一個屬性,我們在某些網站應都見過這麼一種效果,當鼠標移入時圖片的寬度和高度會同時發生變化,這樣一種效果就是多物體動畫同時運動效果,也可以叫做多值運動。

  那要怎麼完成這種動畫效果呢?可以先用之前封裝好的運動框架嘗試一下,既然是同時改變 div 的寬和高,那麼就不能像做鏈式運動那樣,傳入一個回調函數,當某一個值運動結束後再運動下一個值,這樣就成了鏈式運動,而不是同時運動,那如果定義兩次呢,先來看一下。

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>現有框架的問題</title>
 <style>
 div{
     width:100px;
     height:100px;
     background:red;
 }
 </style>
 <script>
 window.onload = function (){
     oBtn = document.getElementById('btn1');
     oDiv = document.getElementById('div1');
     oBtn.onclick = function (){
         startMove(oDiv,'width',300);
         startMove(oDiv,'height',300);
     };
 };
 function getStyle(obj,name){
     if(obj.currentStyle){
         return obj.currentStyle[name];
     }
     else{
         return getComputedStyle(obj,false)[name];
     }
 }
 function startMove(obj,attr,iTarget,fn){
     clearInterval(obj.timer);
     obj.timer = setInterval(function (){
         var cur = 0;
         if(attr == 'opacity'){
             cur = Math.round(parseFloat(getStyle(obj,attr))*100);
         }
         else{
             cur = parseInt(getStyle(obj,attr));
         }
         var speed = (iTarget - cur)/10;
         speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
         if(cur == iTarget){
             clearInterval(obj.timer);
             if(fn)fn();
         }
         else{
             if(attr == 'opacity'){
                 obj.style.opacity = (cur+speed)/100;
                 obj.style.filter = "alpha(opacity: '+ (cur + speed) +')";
             }
             else{
                 obj.style[attr] = cur + speed + "px";
             }
         }
     },30);
 }
 </script>
 </head>
 <body>
 <input id="btn1" type="button" value="運動">
 <div id="div1"></div>
 </body>
 </html>

  上面的代碼,div 的初始寬高都為 100px,我們同時定義了讓 div 的寬和高都運動到 300px,點擊運動按鈕,會發現 div 的高度從 100px 運動到 300px 了,而寬並沒有改變。想要同時改變 div 的寬和高,而 attr 參數只能傳一個,如果定義兩次,系統會執行後邊的參數設置,因為執行 startMove 前先清空計時器,剛清空了計時器之後,後便的傳參就會立馬執行,也就是前邊的一個參數被覆蓋了,這是現有框架的一個小問題,不能讓幾個值同時運動,要解決這個問題,可以使用 JSON 完成,現在我們先回憶一下 JSON。

 <script>
 var json = {a:5,b:12};
 for(var i in json){
     alert(i + ' = ' + json[i]);
 }
 </script>

  JSON 是一種輕量級的數據交互格式,JSON 語法是 JS 對象表示語法的一個子集,數據在鍵值對中,是以對值出現的,即 名稱/值,名稱和值由冒號連接,並用逗號分隔,花括號保存對象,方括號保存數組,JSON 值可以是:數字(整數或浮點數)、字符串(包含在雙引號中)、對象(包含在花括號中)、數組(包含在方括號中)、邏輯值(true 或 false)、null。可以使用 for in 循環遍歷 JSON 中的數據,var i in json ,i 是定義的變量,in 就是在什麼裡,那就是在 json 裡循環遍歷,上面的代碼,先彈出 a = 5,再彈出 b = 12,那麼 i 就表示 json 中的名稱,而 json[i] 就表示 json 中某項的值,也就是 json 中變量 i 所對應的值。

  了解了 JSON 之後,那麼我們如何使用 JSON 完成同時運動呢,現有的運動框架有4個參數,分別是 obj(對象)、attr(屬性)、iTarget(目標)、fn(回調函數),在這4個參數中,attr 和 iTarget 也就是屬性值和目標值是一對值,屬性就是 json 中的名稱,讓誰做運動,目標就是值,到哪個位置時結束運動。那麼就是說現有的運動框架,只能改變一對值,而不能實現多對值的變化,如果要實現多對值的變化,就需要用到 JSON 格式,因此就可以把 startMove 寫為 startMove(obj, {attr1 : iTarget1, attr2 : iTarget1}, fn),把參數中的這一對值變成 JSON 的格式,用函數傳參的方式就寫為 startMove(obj, json, fn)。

  使用了 JSON 之後,還需要把 JSON 中的數據循環遍歷出來,再配合 for in 循環,那要怎樣循環呢,之前的運動框架,先要開啟定時器,然後取當前的值,再計算速度,最後檢測停止,完成這一系列的過程,當前物體觸發的運動才算整個完成,那現在我們要完成多對值同時觸發運動,就是讓現在這整個過程多做幾次循環就可以了,下面我們再來看一下代碼的實現。

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>同時運動</title>
 <style>
 div{
     width:100px;
     height:100px;
     background:red;
 }
 </style>
 <script>
 window.onload = function (){
     oBtn = document.getElementById('btn1');
     oDiv = document.getElementById('div1');
     oBtn.onclick = function (){
         startMove(oDiv, {width:300, height:300});
     };
 };
 function getStyle(obj,name){
     if(obj.currentStyle){
         return obj.currentStyle[name];
     }
     else{
         return getComputedStyle(obj,false)[name];
     }
 }
 function startMove(obj, json, fn){
     clearInterval(obj.timer);
     obj.timer = setInterval(function (){
         for(var attr in json){
             //1、取當前值
             var cur = 0;
             if(attr == 'opacity'){
                 cur = Math.round(parseFloat(getStyle(obj,attr))*100);
             }
             else{
                 cur = parseInt(getStyle(obj,attr));
             }
             //2、算速度
             var speed = (json[attr] - cur)/10;
             speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
             //3、檢測停止
             if(cur == json[attr]){
                 clearInterval(obj.timer);
                 if(fn)fn();
             }
             else{
                 if(attr == 'opacity'){
                     obj.style.opacity = (cur+speed)/100;
                     obj.style.filter = "alpha(opacity: '+ (cur + speed) +')";
                 }
                 else{
                     obj.style[attr] = cur + speed + "px";
                 }
             }
         }
     },30);
 }
 </script>
 </head>
 <body>
 <input id="btn1" type="button" value="運動">
 <div id="div1"></div>
 </body>
 </html>

   上面的代碼,點擊運動按鈕後,div 的寬和高同時從 100px 變為 300px。在開啟定時器之後,就使用 for in 循環,將之前框架中 attr 參數定義為一個變量,就是在 json 中循環遍歷 attr,也就是存儲在 json 中的屬性值,此時已經沒有 iTarget 參數了,那麼要設置目標值,就要把原來的 iTarget 改為 json[attr],最後在調用 startMove 時就可以用 JSON 格式傳入多個值了,startMove(oDiv, {width:300, height:300}),這樣就完成了同時運動。

  

6、完美運動框架

  我們已經封裝了同時運動框架,到這一刻,我們離完美的運動框架就已經不遠了,那為什麼還有一點距離呢,我們通過下面的實例來看一下。

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>同時運動</title>
 <style>
 *{margin:0;padding:0}
 div{
     width:200px;
     height:100px;
     background:red;
     border:2px solid black;
     filter:alpha(opacity:30);
     opacity:0.3;
 }
 </style>    
 </head>
 <script>
 window.onload = function (){
     oDiv = document.getElementById('div1');
     oDiv.onmousemove = function (){
         startMove(oDiv, {width:201, height:200, opacity:100});
     };
     oDiv.onmouseout = function (){
         startMove(oDiv, {width:200, height:100, opacity:30});
     };
 };
 function getStyle(obj,name){
     if(obj.currentStyle){
         return obj.currentStyle[name];
     }
     else{
         return getComputedStyle(obj,false)[name];
     }
 }
 function startMove(obj, json, fn){
     clearInterval(obj.timer);
     obj.timer = setInterval(function (){
         for(var attr in json){
             //1、取當前值
             var cur = 0;
             if(attr == 'opacity'){
                 cur = Math.round(parseFloat(getStyle(obj,attr))*100);
             }
             else{
                 cur = parseInt(getStyle(obj,attr));
             }
             //2、算速度
             var speed = (json[attr] - cur)/10;
             speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
             //3、檢測停止
             if(cur == json[attr]){
                 clearInterval(obj.timer);
                 if(fn)fn();
             }
             else{
                 if(attr == 'opacity'){
                     obj.style.opacity = (cur+speed)/100;
                     obj.style.filter = "alpha(opacity: '+ (cur + speed) +')";
                 }
                 else{
                     obj.style[attr] = cur + speed + "px";
                 }
             }
         }
     },30);
 }
 </script>
 <body>
 <div id="div1"></div>
 </body>
 </html>

  上面的代碼,使用了同時運動框架,在其實例中,多值運動並沒有什麼問題,多個值可以同時變化,觀察上面的代碼,div 的初始寬度為 200px,高度為 100px,透明度為 0.3,在鼠標移入後,我們讓他的寬度變為 201px,高度變為 200px,透明度變為 1,這時候就出事了,打開調試工具進行測試,可以看到在鼠標移入時,div 的寬度確實是從 200px 運動到 201px,但是他的高度和透明度遠遠沒有達到我們的要求,我們來分析一下為什麼會出現這種情況呢,只有寬度到達了目標值,而高度和透明度並沒有達到目標值,整個運動就停止了。

  回過頭檢查同時運動框架,我們不難發現,在做檢測停止時,if(cur == json[attr]),我們判斷如果當前值達到目標值時,就關閉定時器,同時運動框架和完美運動框架的距離就產生在這了,我們並沒有去判斷是不是所有的運動都到達了終點,那麼只要有一個運動達到了終點,他就會關閉定時器,那這樣肯定是不行的,就好比參加旅游團,不可能人沒到齊就出發了,這樣肯定是不靠譜的。那要怎麼解決呢,我們需要去判斷是不是所有的運動都到達了目標值,如果所有的運動都達到了目標值,那麼才能關閉定時器,也就是說,如果有沒有達到的,就不能關閉定時器。

  下面我們就來看看完美的運動框架到底長什麼樣。

 <script>
 function startMove(obj, json, fn){
     clearInterval(obj.timer);
     obj.timer = setInterval(function (){
         var bStop = true;    //假設所有的值都達到了目標值,這一次運動就結束了。
         for(var attr in json){
             //1.取當前值
             var cur = 0;
             if(attr == 'opacity'){
                 cur = Math.round(parseFloat(getStyle(obj,attr))*100);
             }
             else{
                 cur = parseInt(getStyle(obj,attr));
             }
             //2.算速度
             var speed = (json[attr]-cur)/6;
             speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
             //3.檢測停止
             //如果有一個值不等於當前值。
             if(cur != json[attr]){
                 bStop = false;
             }
             if(attr == 'opacity'){
                 obj.style.opacity = (cur+speed)/100;
                 obj.style.filter = "alpha(opacity: "+(cur + speed)+")";
             }
             else{
                 obj.style[attr] = cur + speed + "px";
             }
         }
         //如果所有的值都達到了目標值,再關閉定時器。
         if(bStop){
            clearInterval(obj.timer);
             if(fn){
                 fn();
             }
         }
     },30);
 }
 </script>

  我們把同時運動框架稍微完善以下,就是完美的運動框架了,首先在開啟定時器執行循環之前,首先定義一個變量 bStop 值為 true,假設所有值都達到了目標值,那麼就一次運動才算是結束了,然後在做檢測停止時,再做判斷,如果有一個值不等於當前值,也就是有一個值還沒有達到目標值,就說明 bStop 為 false,那麼就讓他繼續執行他的運動,在完成了整個循環之後,還要再做一個判斷,如果所有值都達到了目標值,那麼再關閉定時器。

  到現在,我們這個運動框架就算是真正的完美了,我們可以把這個框架保存為一個單獨的 JS 文件,命名為 move.js,對於上面的問題可以簡單的驗證以下,再做一些其他值的運動。

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>完美運動</title>
 <style>
 *{margin:0;padding:0}
 div{
     width:200px;
     height:100px;
     background:red;
     border:2px solid black;
     filter:alpha(opacity:30);
     opacity:0.3;
 }
 </style>
 <script src="JS/move++.js"></script>
 <script>
 window.onload = function (){
     oDiv = document.getElementById('div1');
     oDiv.onmousemove = function (){
         startMove(oDiv, {width:301, height:402, opacity:100, fontSize:30, borderWidth:5});
     };
     oDiv.onmouseout = function (){
         startMove(oDiv, {width:200, height:100, opacity:30, fontSize:12, borderWidth:2});
     };
 };
 </script>
 </head>
 <body>
 <div id="div1">多值同時運動</div>
 </body>
 </html>

  上面我們使用了完美運動框架,鼠標移入時,同時改變了 div 的寬、高、透明度、字體大小和邊框寬度,鼠標移出時恢復初始定義,一點問題都沒有,非常完美。

  

7、運動框架的總結

  其實我們完全可以在網上找一個封裝好的完美運動框架,或者想要的動畫效果,直接拿來套用,萬能的互聯網,出產任何你能想到的東西,這樣也相當省事,但是對於一個學習者來說,如果所有的東西都直接搬來用,這樣是沒有任何問題,但是你並不了解他具體是怎麼實現的,這個實現的過程才是最值得研究的,也是最有價值的,看著我們寫的代碼,一步步的趨於完美,在這個過程中,才能更好的鍛煉邏輯思維,培養編程的思想,只有最基礎的知識掌握了,根基扎實了,才能在編程的道路上走的更高更遠,所謂一法通則萬法通,多悟多總結,很多東西都可以觸類旁通,道法自然,當然每個人都有自己的學習方式,只要是適合自己的,那就是最好的。

  閒話就不多說了,我們來看一下運動框架的演變過程。

  最簡單的運動框架:startMove(iTarget)

  我們在剛開始定義了一個 startMove 函數,只需要開一個定時器,就可以讓 div 動起來,但是根本停不下來,我們又做了判斷,如果達到了目標值就清空定時器,但是還有一點小問題,雖然到目標位置了,定時器也清空了,但是再點擊按鈕,div 還是會運動一下,因為點擊按鈕之後 startMove 還會被執行一次,所以我們在執行 startMove 時,首先就要清空定時器。之後我們做了勻速運動的分享到側邊欄效果,因為有 2 個目標值,我們定義了 startMove 和 stopMove 兩個函數,用於內容容器的展示和隱藏,這兩個函數長的幾乎一樣,具有相同的功能,我們又做了代碼優化,把不同的東西找出來,用參數的方式傳入。

  起初我們給 startMove 定義了 2 個參數,speed 和 iTarget,在功能相同的情況下,傳入的參數越少越好,目標值參數是不可以省略的,因為得有一個終點,而速度值是可以省略的,因為不管速度快還是慢,都會到達終點。

  我們還說了側邊欄分享效果使用緩沖運動做效果更好。

  多物體運動框架:startMove(obj, iTarget)

  想讓多個物體運動起來,只需要再傳入一個參數,也就是對象參數,我們傳入哪個對象,就運動哪個對象。

  任意值運動框架:startMove(obj, attr, iTarget)

  多個物體運動起來還不夠,如果想讓任意屬性值運動,那就再傳入一個參數,也就是屬性參數,我們傳入哪個屬性,哪個屬性就發生變化。

  我們還了解了 offset 屬性的一個 bug,我們想讓 div 的寬度逐漸變窄時,給他加了邊框屬性,不但沒有變窄,反而是逐漸變寬了,因為 offset 屬性受到其他屬性的影響,他判斷當前的屬性值為寬度值加上邊框值,最好的解決辦法就是獲取非行間樣式,我們又封裝了一用於獲取非行間樣式的函數 getStyle。

  鏈式運動框架:startMove(obj, attr, iTarget, fn)

  鏈式運動就是讓寬變完之後再變高,高變完之後再變其他屬性,一環扣一環,那麼使用回調函數就可以解決了,當一次運動結束後,再調用一次 startMove,開始一個新的運動,那麼就給他再傳一個參數。

  同時運動框架:startMove(obj, json)

  之前的框架都做的是單一的運動,那麼既想讓寬變化,也想讓高變化,最好的辦法就是使用 JSON 格式,我們簡單的回憶了一下 JSON,他是一種輕量級的數據交換格式,在 startMove 這4個參數中,分別是對象、屬性、目標值、回調函數,屬性值和目標值是一對值,那麼就以 JSON 的方式傳入,再使用 for in 循環執行整個運動,就可以實現多值的運動了。

  完美運動框架:startMove(obj, json, fn)

  在封裝了同時運動框架時,我們離完美運動框架就差一個判斷了,同時運動框架,在檢測停止時,判斷如果是達到了目標值就清空定時器,而如果有一個值達到了目標值,其他值並未達到目標值,他也會清空定時器,這顯然是不科學的,這並不是先到先到,所以我們定義了一個變量,假設所有值都達到了目標值,這一次運動才算結束,然後再做判斷,如果有一個值沒有達到目標值,那麼就繼續執行他的運動,最後直到所有值都達到目標值了,再關閉定時,這就完成了完美運動框架的封裝。

  

  現在,我們就可以使用完美運動框架來完成網站中一些常見的動畫效果了,比如圖片輪播、幻燈片展示、以及微博實時滾動效果等。

 

 

 

 

 

 

 

  

 

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved