JS運動從入門到興奮1


        hello,我是沐晴,一個充滿了才華,卻靠了照騙走江湖的前端妹子。在這個充滿PS的年代,這你們都信,哈哈,廢話不多說,今天要分享的是關注JS運動的知識。樓主一直認為,不管學習什么,核心思想才是王道,掌握了思想,不管需求怎么改變,我們都能把它玩弄於股掌之中。。。下面我們就進入運動的世界吧。

        首先來看最基礎的運動:單個物體向右勻速運動到某一點停止

       例子:一個按鈕,一個div,點擊按鈕,讓div向右運動到某一個位置暫停  

// 原理: 1 獲取物體當前的位置 oDiv.offsetleft  // 2 利用定時器,每隔一定的時間,讓物體位置增加相同的距離(10px)。 // 3 判斷物體是否移動到指定位置(500px),如果到達,就清除定時器

var oBtn = document.getElementById('btn'); var oDiv = document.getElementById('div1'); var iTimer = null; oBtn.onclick = function() { iTimer = setInterval(function() { if (oDiv.offsetLeft == 500) { clearInterval(iTimer); } else { oDiv.style.left = oDiv.offsetLeft + 10 + 'px'; } }, 30); } // 存在問題: 1 當我們一直不停的點按鈕的時候,物體運動速度會加快。 // 原因:我們每點擊一次按鈕,就會開啟一個定時器,有時候上次定時器還沒清除, 這個就開啟了,當多個定時器一起物體運動就會加快。 // 解決: 在點擊按鈕的時候,先清除上次的定時器。保證運動中,只有一個定時器在執行。 // 2 當我們指定的位置不是500,是別的位置的時候,有可能物體停不下來。 // 原因:物體每次向前移動10px,不一定就正好到我們指定的位置,除非這個數字剛好整 除每次移動的距離。其實也不算是個問題,但是個需要注意的點。 // 解決:在作條件判斷的時候,判斷范圍,而不是指定的位置。(oDiv.offsetLeft >= 500) 

所以我們可以知道運動需要注意1 運動前清除上次定時器

                                              2 開啟定時器,指向運動

                                              3  運動中指定運動的形式(勻速緩存還是什么的),並且加入條件判斷,以便停止

封裝函數:對於以上運動的改進代碼,我就不做具體的示范了,我們直接來進行簡單的封裝。在封裝函數中順便改進上面的函數了。

var oBtn = document.getElementById('btn'); var oDiv = document.getElementById('div1'); var iTimer = null; oBtn.onclick = function() { startMove(); } // 封裝后的函數
function startMove() { clearInterval(iTimer); //運動前清除定時器
    iTimer = setInterval(function() { if (oDiv.offsetLeft == 500) { clearInterval(iTimer); //滿足條件清除定時器
            } else { oDiv.style.left = oDiv.offsetLeft + 10 + 'px'; //每次向前移動
 } }, 30); } 

擴展需求1:根據上面的函數我們可以實現,一個物體向右運動到某一個位置。那么我們現在如果有兩個物體,一個向左運動,一個向右運動,且運動的目的地是不同的,那么上面的函數是不是就不滿足了呢?

改進:多物體多方向運動

// 函數封裝原則: 1 不變的不動  // 2 改變的東西作為參數 // 3 改變太多的進行條件判斷
 
// 改變的是運動的物體(obj)運動方向(iSpeed)和運動的目標(iTarget) ,如下作為參數
 oDiv1.onmouseover = function() { startMove(this,0, 10); } oDiv1.onmouseout = function() { startMove(this,-100, -10); } function startMove(obj, iTarget, iSpeed) { clearInterval(iTimer); iTimer = setInterval(function() { if (obj.offsetLeft == iTarget) { clearInterval(iTimer); } else { obj.style.left = obj.offsetLeft + iSpeed + 'px'; } }, 30); }

擴大需求2:現在我們不僅僅改變的是物體左右移動,我們想讓一個物體的透明度也能改變。能作出淡入淡出的效果。

存在的問題是

   問題1 : 在css中,不同瀏覽器對透明度的設置方式不同。

IE:  filter: alpha(opacity=30);(0-100) 其他: opacity: 0.3;(0-1// 所以存在的問題是:用哪種方式來判斷透明度是否到達我們所指定的目標,是用0-1,還是0-100來判斷呢?

在這里要普及一個知識 : alert(0.1+0.2) // 0.30000004 alert(0.2+0.7) // 0.89999999 // 看上面的代碼,會發現並沒有像我們想象中彈出0.3,和0.9,是因為在JS中對於小數//的計算結果,是近似值。所以我們用0-100判斷更加准確。

  問題2 :如何獲取到物體的透明度,用obj.style.opacity 嗎?

     這里要說明一個知識點,在JS中用style只能獲取行間樣式。 不能獲取css中的樣式。這個時候我們用什么呢?下面的兩個方法可以獲取非行間樣式,有兼容問題:   

     1  getComputedStyle: getComputedStyle(element,隨意值(寫什么都可以,比如false)).attr:這個獲取的是計算機計算后的樣式,不能獲取復合樣式,只有標准瀏覽器支持IE6 7 8不兼容。

     2  currentStyle :currentStyle[attr],只有IE6 7 8兼容 ,可以獲取非行間樣式,還可以獲取自定義樣式。

     所以需要做一下兼容,我們可以封裝成一個函數,如下:

function getStyle ( obj, attr ) { if(obj.currentStyle){ obj.currentStyle[attr] }else{ getComputedStyle( obj )[attr]; } } //簡寫方式
function getStyle ( obj, attr ) { return obj.currentStyle?obj.currentStyle[attr] : getComputedStyle( obj )[attr]; }

//注意 IE下雖然沒有opacity,但也能獲取到opacity值,主要是因為
currentStyle的原因,它能讀取到任何樣式的值,哪怕不存在

所以對於透明度的改變,我們可以封裝成如下的函數:

function startMove2(obj, iTarget, iSpeed) { clearInterval(iTimer); var iCur = 0;       //用來存放當前的透明度值
   iTimer = setInterval(function() { // iCur = getStyle( obj, 'opacity' ) * 100; // 因為getStyle取出來的是小數,乘以100, 是29.999900000045 這樣的數,這樣我們無法和iTarget進行判斷,所以我們可以進行四舍五入,如下:
       iCur = Math.round(getStyle( obj, 'opacity' ) * 100);        if (iCur == iTarget) { clearInterval(iTimer); } else { //對不同的瀏覽器進行分別處理
           obj.style.opacity = (iCur + iSpeed) / 100; obj.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')'; } }, 30); }

這只是改變透明度的函數封裝,要是能把透明度的封裝函數和前面的運動封裝函數,結合起來,不就能封裝成一個可以改變透明度,也可以改變位置的函數了嗎。。。。

那么依照我們的合並原則,找出兩個封裝函數的不同之處:

運動的屬性不同(attr)

2 因為不同的屬性處理不同,其實主要是透明度處理是有差別的,其他寬高改變位置改變其實都一樣)這個時候的差別,可以采用判斷來解決

 

// 還解決了解決了一個問題是因為之前只有一個定時器,現在因為有多個屬性可以運動了,所以就不能只用一個定時器了去控制了,每個物體運動的時候就應該有自己的定時器,所以就把定時器就用obj.timer上,這樣每個物體都不同啦。

function startMove(obj, attr, iTarget, iSpeed) { clearInterval(obj.iTimer); var iCur = 0; obj.iTimer = setInterval(function() { //對透明度進行判斷如果是就進行上面的處理,不是就進行下面的處理 if (attr == 'opacity') { iCur = Math.round(getStyle( obj, 'opacity' ) * 100); } else { iCur = parseInt(getStyle(obj, attr)); } if (iCur == iTarget) { clearInterval(obj.iTimer); } else { if (attr == 'opacity') { obj.style.opacity = (iCur + iSpeed) / 100; obj.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')'; } else { obj.style[attr] = iCur + iSpeed + 'px'; } } }, 30); } function getStyle(obj, attr) { if (obj.currentStyle) { return obj.currentStyle[attr]; } else { return getComputedStyle(obj, false)[attr]; } }

 

不過上面的函數,還是不能滿足某些需求,比如我想要一個物體的兩個屬性同時運動,下面這樣是實現不了的。

//下面的運動會清除掉上面的定時器
 startMove(this, 'width', 200, 10); startMove(this, 'height', 200, 10);

那么怎么樣實現這兩個屬性可以同時運動呢?這個時候我們可以考慮用json的格式

 


oDiv1.onclick = function() { startMove(this, { width : 200, height: 300 }, 10); } function startMove(obj, json, iSpeed) { clearInterval(obj.iTimer); var iCur = 0; obj.iTimer = setInterval(function() { //定時器每走一下就要把要運動的屬性都推進一次 for ( var attr in json ) { var iTarget = json[attr];//把我們傳進來的屬性值賦給目標值 if (attr == 'opacity') { iCur = Math.round(getStyle( obj, 'opacity' ) * 100); } else { iCur = parseInt(getStyle(obj, attr)); } if (iCur == iTarget) { clearInterval(obj.iTimer); } else { if (attr == 'opacity') { obj.style.opacity = (iCur + iSpeed) / 100; obj.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')'; } else { obj.style[attr] = iCur + iSpeed + 'px'; } } } }, 30); } function getStyle(obj, attr) { if (obj.currentStyle) { return obj.currentStyle[attr]; } else { return getComputedStyle(obj, false)[attr]; } }

存在問題:上面的運動速度都是一樣的,但是傳進去的目標點是不一樣的,這個時候就會有一個屬性先到,那么運動就終止了。所以我們需要解決的是,當所有的屬性都到達了目標點,才讓運動終止。

解決:定義一個開關,每次運動前假設它是真的,在運動中,只要有一個屬性沒運動到終點,就把開關變成假,在循環外面進行判斷,當所有屬性都運動到終點了,開關肯定都是真的,就會清除定時器了。

oDiv1.onclick = function() { startMove(this, { width : 200, height: 300 }, 10); } function startMove(obj, json, iSpeed) { clearInterval(obj.iTimer); var iCur = 0; obj.iTimer = setInterval(function() { //定時器每走一下就要把要運動的屬性都推進一次
            var iBtn = true;//每一次運動前都初始化為真,就是假設所有屬性都到了 for ( var attr in json ) { var iTarget = json[attr];//把我們傳進來的屬性值賦給目標值
                
                  if (attr == 'opacity') { iCur = Math.round(getStyle( obj, 'opacity' ) * 100); } else { iCur = parseInt(getStyle(obj, attr)); } if (iCur != iTarget) { iBtn = false; //如果有一個屬性沒到,就把這個開關變成假if (attr == 'opacity') { obj.style.opacity = (iCur + iSpeed) / 100; obj.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')'; } else { obj.style[attr] = iCur + iSpeed + 'px'; } } } //在這里來看下,所有屬性是不是都到了目標點,當這里變真的了,說明所有屬性都到了目標點,就可以清除定時器了
            if (iBtn) { clearInterval(obj.iTimer); } }, 30); } function getStyle(obj, attr) { if (obj.currentStyle) { return obj.currentStyle[attr]; } else { return getComputedStyle(obj, false)[attr]; } }

擴大需求:假如我們這次實現的不是多個屬性同時運動,我希望先把高改變,接着再改變寬,這個時候,我們需要的就是個回調函數了(fn)。za

oDiv1.onclick = function() { startMove(this, { width : 200 }, 10, function() { startMove(this, { height : 200 }, 10); }); } function startMove(obj, json, iSpeed, fn) { clearInterval(obj.iTimer); var iCur = 0; obj.iTimer = setInterval(function() { var iBtn = true; for ( var attr in json ) { var iTarget = json[attr]; if (attr == 'opacity') { iCur = Math.round(getStyle( obj, 'opacity' ) * 100); } else { iCur = parseInt(getStyle(obj, attr)); } if (iCur != iTarget) { iBtn = false; if (attr == 'opacity') { obj.style.opacity = (iCur + iSpeed) / 100; obj.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')'; } else { obj.style[attr] = iCur + iSpeed + 'px'; } } } if (iBtn) { clearInterval(obj.iTimer); fn && fn.call(obj); //如果回調函數存在再執行,call方向為了修正this的指向,之前關於this文章有講過這個用法。
 } }, 30); } function getStyle(obj, attr) { if (obj.currentStyle) { return obj.currentStyle[attr]; } else { return getComputedStyle(obj, false)[attr]; } }

 

寫着寫着,才發現運動的東西認真講起來還是很多的,只靠文字也不容易講清楚,下篇再繼續寫js緩沖運動,碰撞運動,彈性等,由於時間倉促,可能寫的比較亂,歡迎大家來找錯。。。。。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM