1,運動原理
Js運動,本質來說,就是讓 web 上 DOM 元素動起來。而想要 DOM 動起來,改變其自身的位置屬性,比如高寬,左邊距,上邊距,透明度等。動畫的原理就是把不同狀態的物體,串成連續的樣子,就像一本書,畫了幾個小人,然后一翻書,就看見小人在動。js動畫也一樣。不同狀態的DOM,用定時器控制,就能得到動畫效果。
- window.onload = function(){
- var oBtn = document.getElementById('btn');
- oBtn.onclick = function(){
- var oDiv = document.getElementById('div1');
- //設置定時器
- setInterval(function(){
- //改變物體位置
- oDiv.style.left = oDiv.offsetLeft + 10 + 'px';
- },30)
- }
- }
上述代碼,點擊btn之后,就能是物體向左運動。可是會一直向右動,不會停止。因此需要創立一個停止的條件。在條件符合的情況下,清楚定時器。其中對於目標點的判斷,尤為重要。
- window.onload = function(){
- var oBtn = document.getElementById('btn');
- oBtn.onclick = function(){
- var oDiv = document.getElementById('div1');
- //設置定時器
- var timer = setInterval(function(){
- //判斷停止條件
- if(oDiv.offsetLeft > 300){
- clearInterval(timer);
- }else{
- //改變物體位置
- oDiv.style.left = oDiv.offsetLeft + 10 + 'px';
- document.title = oDiv.offsetLeft;
- }
- },30);
- }
- }
上述代碼中,但物體的位置大於300的時候,將停止運動。但是上述代碼還有個問題,就是連續點擊按鈕,物體會運動越來越快。因為每點擊一次,就開了一個定時器,累加的定時器。造成運動混亂。
2,運動框架 (滑入滑出,淡入淡出)
為了解決上述問題,則必須在開啟定時器之前,先清除定時器,因此需要一個全局變量 timer保存定時器。如下面代碼。
- window.onload = function(){
- var oBtn = document.getElementById('btn');
- oBtn.onclick = function(){
- startMove();
- }
- }
- 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';
- document.title = oDiv.offsetLeft;
- }
- },30);
- }
此外,在改變物體位置的時候,那個 “10”則是更改的數量,其實也就是速度。如果更改速度,運動的快慢就能確定。因此,運動框架的原理,基本步驟為。
-
先清除定時器
-
開啟定時器,計算速度
-
判斷停止條件,執行運動
- var timer = null;
- function startMove(){
- var oDiv = document.getElementById('div1');
- clearInterval(timer);
- //計算速度
- var iSpeed = 10;
- //設置定時器
- timer = setInterval(function(){
- //判斷停止條件
- if(oDiv.offsetLeft > 300){
- clearInterval(timer);
- }else{
- //改變物體位置
- oDiv.style.left = oDiv.offsetLeft + iSpeed + 'px';
- document.title = oDiv.offsetLeft;
- }
- },30);
- }
對於停止條件,寫死在里面了,所以需分離出參數。下面是一個分享到的例子。主要是根據目標判斷速度的正負。從而在鼠標滑入畫出時候進行運動/恢復的效果。
- window.onload = function(){
- var oDiv = document.getElementById('div1');
- oDiv.onmouseover = function(){
- startMove(0);
- }
- oDiv.onmouseout = function(){
- startMove(-100);
- }
- }
- var timer = null;
- var iSpeed;
- function startMove(iTatget){
- var oDiv = document.getElementById('div1');
- clearInterval(timer);
- timer = setInterval(function(){
- //計算速度
- if(iTatget -oDiv.offsetLeft > 0){
- iSpeed = 10;
- }else{
- iSpeed = -10;
- }
- if(oDiv.offsetLeft == iTatget){
- clearInterval(timer);
- }else{
- oDiv.style.left = oDiv.offsetLeft + iSpeed + 'px';
- }
- document.title = oDiv.offsetLeft;
- },30)
- }
另外一個小例子,淡入淡出,即改變物體的透明度,由於沒有像原生的位置屬性那樣的offsetLset. 需要一個變量來保存透明度的值,用來和速度加減,最后付給元素的透明度樣式。從而實現淡入淡出效果。
- window.onload = function(){
- var oImg = document.getElementById('img1');
- oImg.onmouseover = function(){
- startMove(100);
- }
- oImg.onmouseout = function(){
- startMove(30);
- }
- }
- var timer = null;
- //保存透明度的數字值
- var alpha = 30;
- function startMove(iTarget){
- var oDiv = document.getElementById('img1');
- clearInterval(timer);
- timer = setInterval(function(){
- var iSpeed = 0;
- if(alpha > iTarget){
- iSpeed = -1;
- }else{
- iSpeed = 1;
- }
- if(alpha == iTarget){
- clearInterval(timer);
- }else{
- //改變透明度速度值
- alpha += iSpeed;
- oDiv.style.filter = 'alpha(opacity:'+ alpha+')';
- oDiv.style.opacity = alpha/100;
- document.title = alpha;
- }
- },30)
- }
3,緩沖運動
緩沖運動原理就是,改變速度的值。每次累加的速度值變小,就是會是整個物體看起來越來越慢,以至於最后停掉。相當於改變使物體具有一個加速度。這個加速度,可以由物體當前位置和目標位置之間的距離得到,因為兩者之間的距離一直在變小,所以速度也一直在變小。如下:
- window.onload = function(){
- var btn = document.getElementsByTagName('input')[0];
- btn.onclick = function(){
- startMove(300);
- }
- }
- var timer = null;
- function startMove(iTarget){
- var oDiv = document.getElementById('div1');
- clearInterval(timer);
- timer = setInterval(function(){
- //求出帶有變化的速度
- var iSpeed = (iTarget - oDiv.offsetLeft) / 8;
- if(oDiv.offsetLeft == iTarget){
- clearInterval(timer);
- }else{
- oDiv.style.left = oDiv.offsetLeft + iSpeed + 'px';
- }
- document.title = oDiv.offsetLeft + '...' + iSpeed;
- },30);
- }
上述方法可以得到緩沖運動,但是實際運行效果,物體並沒有在 300的位置停掉,而是在 293的位置就停掉了。究其原因。因為當物體的速度小於1 的時候。物體位置為293。此時計算的速度是 0.875.通過 oDiv.style.left = oDiv.offsetLeft + iSpeed + 'px'; 物體的位置為 293.875.可是計算機不能識別小數,將小數省略了。此時的 位置offsetLeft仍然是 293.再計算一次,還是同樣的結果。定時器沒有關掉,但是物體的位置卻再也無法改變,故停留在了 293的位置。解決方案,就是將速度進行向上取整。但是,像上述運動,速度是正的,可是,當速度是負的時候,就同樣會有相同的結果,因此需要在速度為負的時候,向下取整。
- function startMove(iTarget){
- var oDiv = document.getElementById('div1');
- clearInterval(timer);
- timer = setInterval(function(){
- var iSpeed = (iTarget - oDiv.offsetLeft) / 8;
- //對正的速度向上取整,負的速度向下取整
- iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);
- if(oDiv.offsetLeft == iTarget){
- clearInterval(timer);
- }else{
- oDiv.style.left = oDiv.offsetLeft + iSpeed + 'px';
- }
- document.title = oDiv.offsetLeft + '...' + iSpeed;
- },30);
- }
4.多物體運動
下一步,就是處理多物體運動,運動函數里面每次都要選取一個元素加事件。如果需要對多個物體進行同樣的運動, 需要將運動對象作為參數傳進來。
- window.onload = function(){
- var aDiv = document.getElementsByTagName('div');
- for(var i=0;i<aDiv.length;i++){
- aDiv[i].onmouseover = function(){
- startMove(this,300);
- }
- aDiv[i].onmouseout = function(){
- startMove(this,100);
- }
- }
- }
- var timer = null;
- function startMove(obj,iTarget){
- clearInterval(timer);
- timer = setInterval(function(){
- var iSpeed = (iTarget - obj.offsetWidth) / 8;
- iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);
- if(obj.offsetWidth == iTarget){
- clearInterval(timer);
- }else{
- obj.style.width = obj.offsetWidth + iSpeed + 'px';
- }
- },30)
- }
通過循環物體,將物體的 this傳給運動函數,使得多物體可以運動。但是這樣有一個弊端,即當滑入第一個運動的時候,開啟了定時器。如果此時,滑入另外一個物體,將會清理上一個定時器。這就造成了,上一次運動,很有可能還沒完成結束,定時器就沒關閉了。解決的方法,每個運動的物體,都能開了一個屬於自己的定時器。因此,把定時器當成物體的屬性。清理的時候也就是清理自己的定時器。
- window.onload = function(){
- var aDiv = document.getElementsByTagName('div');
- for(var i=0;i<aDiv.length;i++){
- aDiv[i].onmouseover = function(){
- startMove(this,300);
- }
- aDiv[i].onmouseout = function(){
- startMove(this,100);
- }
- }
- }
- function startMove(obj,iTarget){
- //將定時器,變成物體的屬性,類似給物體添加索引
- clearInterval(obj.timer);
- obj.timer = setInterval(function(){
- var iSpeed = (iTarget - obj.offsetWidth) / 8;
- iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);
- if(obj.offsetWidth == iTarget){
- clearInterval(obj.timer);
- }else{
- obj.style.width = obj.offsetWidth + iSpeed + 'px';
- }
- },30)
- }
多物體的淡入淡出的時候,也有類似的問題。因為修改透明度的時候,是先用一個變量保存透明度,必須針對每個物體設立透明度值屬性。
- 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 iSpeed = (iTarget - obj.alpha) / 8;
- iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);
- if(obj.alpha == iTarget){
- clearInterval(obj.timer);
- }else{
- obj.alpha += iSpeed;
- obj.style.filter = 'alpha(opacity:'+obj.alpha+')';
- obj.style.opacity = obj.alpha / 100;
- }
- document.title = obj.alpha;
- },30);
- }
4.1 位置屬性的bug
offsetWidth 或者 offsetHeight 等位置屬性,一旦給他們加上 border。則會有詭異的現象出現。
- window.onload = function(){
- var oDiv = document.getElementById('div1');
- setInterval(function(){
- oDiv.style.width = oDiv.offsetWidth - 1 + "px";
- },30)
- }
例如 oDiv.style.width = oDiv.offsetWidth - 1 + 'px'; 如果給 oDiv 的width 為一百,border 為 1.則這個物體的 width是100px;offsetWidth 為102px;帶入公式之后,即減一之后。100 = 102 - 1 ,反而等於101;即 物體本來要減小,事實卻增大了。解決的方案就是,加減的時候,必須使用物體的內聯樣式。但是 火狐 和 IE 又有兼容模式。解決方案如下:
- window.onload = function(){
- var oDiv = document.getElementById('div1');
- setInterval(function(){
- oDiv.style.width = parseInt(getStyle(oDiv,'width')) - 1 + 'px';
- },30)
- }
- function getStyle(obj,attr){
- if(obj.currentStyle){
- return obj.currentStyle[attr];
- }else{
- return getComputedStyle(obj,false)[attr];
- }
- }
其中,getStyle函數,傳入一個元素對象,和其 css 屬性,獲取的是元素的樣式,即 witdh 100px;因此需要parseInt轉換
5.任意值運動
通過 getStyle 函數,可以獲取元素的樣式,還可也通過 attr 制定需要修改的 css屬性。這樣就能是物體有不同的運動形式。
- window.onload = function(){
- var aDiv = document.getElementsByTagName('div');
- aDiv[0].onmouseover = function(){
- startMove(this,'width',300);
- }
- aDiv[0].onmouseout = function(){
- startMove(this,'width',100);
- }
- aDiv[1].onmouseover = function(){
- startMove(this,'height',100);
- }
- aDiv[1].onmouseout = function(){
- startMove(this,'height',50);
- }
- }
- function getStyle(obj,attr){
- if(obj.currentStyle){
- return obj.currentStyle(attr);
- }else{
- return getComputedStyle(obj,false)[attr];
- }
- }
- function startMove(obj,attr,iTarget){
- clearInterval(obj.timer);
- obj.timer = setInterval(function(){
- var iCur = parseInt(getStyle(obj,attr));
- var iSpeed = (iTarget - iCur) / 8;
- iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);
- if(iCur == iTarget){
- clearInterval(obj.timer);
- }else{
- obj.style[attr] = iCur + iSpeed + 'px';
- }
- },30)
- }
5.1 任意值完美版
上述版本,還不能處理透明度的任意值,因此需要增加額外的兼容hack。
- window.onload = function(){
- var aDiv = document.getElementsByTagName('div');
- aDiv[0].onmouseover = function(){
- startMove(this,'opacity',100);
- }
- aDiv[0].onmouseout = function(){
- startMove(this,'opacity',30);
- }
- }
- function getStyle(obj,attr){
- if(obj.currentStyle){
- return obj.currentStyleattr[attr];
- }else{
- return getComputedStyle(obj, false)[attr];
- }
- }
- function getStyle(obj, attr){
- if(obj.currentStyle) {
- return obj.currentStyle[attr];
- }else{
- return getComputedStyle(obj, false)[attr];
- }
- }
- function startMove(obj,attr,iTarget){
- clearInterval(obj.timer);
- obj.timer = setInterval(function(){
- var iCur = 0;
- if(attr == 'opacity'){
- iCur = parseInt(parseFloat(getStyle(obj, attr))*100);
- }else{
- iCur = parseInt(getStyle(obj,attr));
- }
- var iSpeed = (iTarget - iCur) / 8;
- iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);
- if(iCur == iTarget){
- clearInterval(obj.timer);
- }else{
- if(attr=='opacity'){
- iCur += iSpeed
- obj.style.filter='alpha(opacity:' + iCur + ')';
- obj.style.opacity=iCur / 100;
- }
- else{
- obj.style[attr]=iCur+iSpeed+'px';
- }
- document.title = obj.style[attr];
- }
- },30)
- }
6.鏈式運動
我們的運動框架到目前為止,基本功能都能實現了。現在拓展。所謂鏈式運動,即運動接着運動。當運動停止的時候,如果回調一個函數。回調一個運動函數,就能出現這樣的效果。因此傳入一個函數作為回調函數。
- window.onload = function(){
- var oDiv = document.getElementById('div1');
- oDiv.onclick = function(){
- startMove(this,'width',300,function(){
- startMove(oDiv,'height',300,function(){
- startMove(oDiv,'opacity',100)
- })
- })
- }
- }
- function getStyle(obj,attr){
- if(obj.currentStyle){
- return obj.currentStyleattr[attr];
- }else{
- return getComputedStyle(obj, false)[attr];
- }
- }
- function startMove(obj,attr,iTarget,fn){
- clearInterval(obj.timer);
- obj.timer = setInterval(function(){
- var iCur = 0;
- if(attr == 'opacity'){
- iCur = parseInt(parseFloat(getStyle(obj, attr))*100);
- }else{
- iCur = parseInt(getStyle(obj,attr));
- }
- var iSpeed = (iTarget - iCur) / 8;
- iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);
- if(iCur == iTarget){
- clearInterval(obj.timer);
- //回調函數
- if(fn) fn();
- }else{
- if(attr=='opacity'){
- iCur += iSpeed
- obj.style.filter='alpha(opacity:' + iCur + ')';
- obj.style.opacity=iCur / 100;
- }
- else{
- obj.style[attr]=iCur+iSpeed+'px';
- }
- document.title = obj.style[attr];
- }
- },30)
- }
7.同時運動
目前為止,我們的運動框架還有個小缺點,就是不能同時該兩個屬性進行運動,比如同時更改寬和高。這個要求傳入的屬性是不同的幾個值。則考慮傳入一個 json用來保存需要更改的屬性。
- window.onload = function(){
- var oDiv = document.getElementById('div1');
- oDiv.onclick = function(){
- startMove(this,{'width':300,'height':400});
- }
- }
- function getStyle(obj, attr){
- if(obj.currentStyle) {
- return obj.currentStyle[attr];
- }else{
- return getComputedStyle(obj, false)[attr];
- }
- }
- function startMove(obj,json,fn){
- clearInterval(obj.timer);
- obj.timer = setInterval(function(){
- // 循環json
- for(var attr in json){
- var iCur = 0;
- if(attr == 'opacity'){
- iCur = parseInt(parseFloat(getStyle(obj, attr))*100);
- }else{
- iCur = parseInt(getStyle(obj,attr));
- }
- var iSpeed = (json[attr] - iCur) / 8;
- iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);
- if(iCur == json[attr]){
- clearInterval(obj.timer);
- if(fn) fn();
- }else{
- if(attr=='opacity'){
- iCur += iSpeed
- obj.style.filter='alpha(opacity:' + iCur + ')';
- obj.style.opacity=iCur / 100;
- }
- else{
- obj.style[attr]=iCur+iSpeed+'px';
- }
- document.title = obj.style[attr];
- }
- }
- },30)
上述代碼,可以解決了同時運動的問題。但是還是有一個bug。比如,同時運動的某個屬性,如果變化很小,馬上就停止了,即關掉了定時器。那么會造成其他屬性的變化也停止。因為這些屬性都共用了一個定時器。因此需要判斷,假設有三個人要來,然后一起去爬山。三個人有的先來,有的后來,只要三個人都到齊了,才出發。也就是只有三個屬性都到了目標值,才關定時器。一開始,設立一個檢查量,為真。假設所有人都到了,然后循環,只有有一個人沒有到,檢查就為假。直到所有的都到了,檢測為真。則停止定時器。
- window.onload = function(){
- var oDiv = document.getElementById('div1');
- oDiv.onclick = function(){
- startMove(this,{'width':102,'height':400,'opacity':100});
- }
- }
- function getStyle(obj, attr){
- if(obj.currentStyle) {
- return obj.currentStyle[attr];
- }else{
- return getComputedStyle(obj, false)[attr];
- }
- }
- function startMove(obj,json,fn){
- clearInterval(obj.timer);
- obj.timer = setInterval(function(){
- var bStop = true;
- for(var attr in json){
- //取當前值
- var iCur = 0;
- if(attr == 'opacity'){
- iCur = parseInt(parseFloat(getStyle(obj, attr))*100);
- }else{
- iCur = parseInt(getStyle(obj,attr));
- }
- //計算速度
- var iSpeed = (json[attr] - iCur) / 8;
- iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);
- //檢測停止
- if(iCur != json[attr]){
- bStop = false;
- }
- if(attr=='opacity'){
- iCur += iSpeed
- obj.style.filter='alpha(opacity:' + iCur + ')';
- obj.style.opacity=iCur / 100;
- }
- else{
- obj.style[attr]=iCur+iSpeed+'px';
- }
- }
- if(bStop){
- clearInterval(obj.timer);
- if(fn) fn();
- }
- },30)
- }
再循環外定義一個 標志變量 bStop = true。用來表示所有屬性到達目標值。等循環結束了,如果這個值是真的,則停止定時器。因為,每次運行定時器,都會初始化這個值。循環的過程中,只要有一個沒有到,bStop就被設定為 false。如果某個到了,此時 iCur != json[attr],表示速度為0 后面執行的結果,也不會有變化。只有所有的都達到目標值。循環則不再改變 bStop的值。此時,只要下一次運行定時器。就是初始化 bStop為真。而循環因為都到了,所以速度為0 也就再也沒有變化。循環結束,sBstop還是真,表示所有都到了。因此此時結束定時器。
最后附上完美運動框架,封裝成 move.js 就可以調用了。
- /**
- * @author rsj217
- * getStyle 獲取樣式
- * startMove 運動主程序
- */
- function getStyle(obj, attr){
- if(obj.currentStyle) {
- return obj.currentStyle[attr];
- }else{
- return getComputedStyle(obj, false)[attr];
- }
- }
- function Move(obj,json,fn){
- //停止上一次定時器
- clearInterval(obj.timer);
- //保存每一個物體運動的定時器
- obj.timer = setInterval(function(){
- //判斷同時運動標志
- var bStop = true;
- for(var attr in json){
- //取當前值
- var iCur = 0;
- if(attr == 'opacity'){
- iCur = parseInt(parseFloat(getStyle(obj, attr))*100);
- }else{
- iCur = parseInt(getStyle(obj,attr));
- }
- //計算速度
- var iSpeed = (json[attr] - iCur) / 8;
- iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);
- //檢測同時到達標志
- if(iCur != json[attr]){
- bStop = false;
- }
- //更改屬性,獲取動畫效果
- if(attr=='opacity'){
- iCur += iSpeed
- obj.style.filter='alpha(opacity:' + iCur + ')';
- obj.style.opacity=iCur / 100;
- }
- else{
- obj.style[attr]=iCur+iSpeed+'px';
- }
- }
- //檢測停止
- if(bStop){
- clearInterval(obj.timer);
- if(fn) fn();
- }
- },30)
- }
