Javascript之封裝運動函數
- 2020 年 3 月 30 日
- 筆記
目錄
本文採取逐步深入的方式講解原生JS封裝運動函數的過程,
封裝結果適用於元素大部分屬性的運動,
運動方式將根據需求持續更新,目前主要支持常用的兩種:勻速運動和緩衝運動。
階段一、僅適用單位帶px屬性的勻速運動
效果圖:
封裝思路:
- 傳入需要運動的屬性
attr
、運動的目標值target_value
、運動速度speed
; - 對速度進行簡單處理
speed = speed || 5
,即不傳入速度參數值時,默認速度為5; - 使用
getComputedStyle()
獲取元素該屬性的當前值now_value
,通過target_value > now_value ? Math.abs(speed) : -Math.abs(speed);
獲取運動的方向; - 通過
Math.abs(target_value - now_value) <= Math.abs(speed)
獲取終止條件,需要終止時關閉定時器並將運動元素送至終點box_ele.style[attr] = target_value + "px";
完整代碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> *{ margin: 0; padding: 0; } .box{ width: 200px; height: 200px; background: skyblue; position: absolute; } </style> </head> <body> <div class="box"></div> <script> var timer = null; function animate(target_value, attr, speed){ var now_value = parseInt(getComputedStyle(box_ele)[attr]); speed = speed || 5; speed = target_value > now_value ? Math.abs(speed) : -Math.abs(speed); clearInterval(timer); timer = setInterval(function(){ if(Math.abs(target_value - now_value) <= Math.abs(speed)){ box_ele.style[attr] = target_value + "px"; clearInterval(timer); }else{ now_value += speed; box_ele.style[attr] = now_value + "px"; } }, 30) } box_ele = document.querySelector(".box"); box_ele.addEventListener("mouseenter", function(){ animate(400, "left"); }) </script> </body> </html>
階段二、可適用單位不帶px屬性(如opacity)的勻速運動
效果圖:
封裝思路:
- 在階段一的基礎上添加對元素運動屬性的判定,若運動屬性為
opacity
,需要對相關值進行處理; - 由於默認速度為5,而
opacity
的範圍為0~1,故需要對目標值和當前值進行處理,即now_value = parseInt(getComputedStyle(box_ele)[attr] * 100);
target_value *= 100;
- 渲染元素運動效果時由
opacity
屬性不帶單位px,故也需要進行處理,即運動時box_ele.style[attr] = now_value / 100;
,終止時box_ele.style[attr] = target_value / 100;
完整代碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> *{ margin: 0; padding: 0; } .box{ width: 200px; height: 200px; background: skyblue; position: absolute; } </style> </head> <body> <div class="box"></div> <script> var timer = null; function animate(target_value, attr, speed){ if(attr === "opacity"){ var now_value = parseInt(getComputedStyle(box_ele)[attr] * 100); target_value *= 100; }else{ var now_value = parseInt(getComputedStyle(box_ele)[attr]); } speed = speed || 5; speed = target_value > now_value ? Math.abs(speed) : -Math.abs(speed); clearInterval(timer); timer = setInterval(function(){ if(Math.abs(target_value - now_value) <= Math.abs(speed)){ if(attr === "opacity"){ box_ele.style[attr] = target_value / 100; }else{ box_ele.style[attr] = target_value + "px"; } clearInterval(timer); }else{ now_value += speed; if(attr === "opacity"){ box_ele.style[attr] = now_value / 100; }else{ box_ele.style[attr] = now_value + "px"; } } }, 30) } box_ele = document.querySelector(".box"); box_ele.addEventListener("mouseenter", function(){ animate(0, "opacity"); }) </script> </body> </html>
階段三、適用於多元素單一屬性的勻速運動
效果圖:
封裝思路:
- 在階段二的基礎上添加參數
ele
,從而可以控制不同元素的運動; - 將定時器放入運動元素對象中,即
ele.timer = setInterval(function(){}
,避免相互之間造成干擾
完整代碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> *{ margin: 0; padding: 0; } .box{ width: 200px; height: 200px; background: skyblue; margin-bottom: 10px; } </style> </head> <body> <div class="box"></div> <div class="box"></div> <div class="box"></div> <script> function animate(ele, target_value, attr, speed){ if(attr === "opacity"){ var now_value = parseInt(getComputedStyle(ele)[attr] * 100); target_value *= 100; }else{ var now_value = parseInt(getComputedStyle(ele)[attr]); } speed = speed || 5; speed = target_value > now_value ? Math.abs(speed) : -Math.abs(speed); // 定時器放入ele對象中,保證每個元素使用自己的定時器互不干擾 clearInterval(ele.timer); ele.timer = setInterval(function(){ if(Math.abs(target_value - now_value) <= Math.abs(speed)){ clearInterval(ele.timer); if(attr === "opacity"){ ele.style[attr] = target_value / 100; }else{ ele.style[attr] = target_value + "px"; } }else{ now_value += speed; if(attr === "opacity"){ ele.style[attr] = now_value / 100; }else{ ele.style[attr] = now_value + "px"; } } }, 30) } var box_eles = document.querySelectorAll(".box"); document.body.onclick = function(){ animate(box_eles[0], 500, "width"); animate(box_eles[1], 200, "margin-left"); animate(box_eles[2], 0.2, "opacity"); } </script> </body> </html>
階段四、適用於多元素單一屬性的勻速或緩衝運動
效果圖:
封裝思路:
- 在階段三的基礎上添加參數
animate_mode
,並設置animate_mode = "uniform_motion"
,即默認為勻速運動; - 根據傳入的運動方式計算速度,若傳入的參數為
"butter_motion"
,速度計算方法為speed = (target_value - now_value) / 10
,即根據距離目標值的距離不斷調整速度,越靠近目標點速度越慢; - 注意速度計算需要放入定時器中,因為只有定時器中的
now_value
在不斷變化。
完整代碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> *{ margin: 0; padding: 0; } .box{ width: 200px; height: 200px; background: skyblue; margin-bottom: 10px; } </style> </head> <body> <div class="box"></div> <div class="box"></div> <div class="box"></div> <script> function animate(ele, target_value, attr, animate_mode = "uniform_motion", speed){ if(attr === "opacity"){ var now_value = parseInt(getComputedStyle(ele)[attr] * 100); target_value *= 100; }else{ var now_value = parseInt(getComputedStyle(ele)[attr]); } // 勻速運動模式下的速度 if(animate_mode === "uniform_motion"){ speed = speed || 5; speed = target_value > now_value ? Math.abs(speed) : -Math.abs(speed); } clearInterval(ele.timer); ele.timer = setInterval(function(){ // 緩衝運動模式下的速度 if(animate_mode === "butter_motion"){ // 根據距離目標值的距離不斷調整速度,越靠近目標點速度越慢 speed = (target_value - now_value) / 10; speed = target_value > now_value ? Math.abs(speed) : -Math.abs(speed); } if(Math.abs(target_value - now_value) <= Math.abs(speed)){ clearInterval(ele.timer); if(attr === "opacity"){ ele.style[attr] = target_value / 100; }else{ ele.style[attr] = target_value + "px"; } }else{ now_value += speed; if(attr === "opacity"){ ele.style[attr] = now_value / 100; }else{ ele.style[attr] = now_value + "px"; } } }, 30) } var box_eles = document.querySelectorAll(".box"); document.body.onclick = function(){ animate(box_eles[0], 500, "width", "butter_motion"); animate(box_eles[1], 200, "margin-left", "butter_motion"); animate(box_eles[2], 0.2, "opacity"); } </script> </body> </html>
階段五、適用於多元素多屬性的勻速或緩衝運動
效果圖:
封裝思路:
- 在階段四的基礎上將屬性參數
attr
、目標值參數target_value
刪除,替換為attr_obj
; - 遍歷傳入的屬性對象,對每個屬性值進行處理,分別設置屬性的目標值
target_value
和當前值now_value
; - 在定時器中遍歷傳入的屬性對象,逐個屬性進行運動;
- 由於運動目標的不一致會讓運動執行次數不同,有可能提前關閉定時器,故某條屬性運動完成時刪除該條屬性數據,直到對象里沒有屬性,關閉定時器。
完整代碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> *{ margin: 0; padding: 0; } .box{ width: 200px; height: 200px; background: skyblue; margin-bottom: 10px; } </style> </head> <body> <div class="box"></div> <script> function animate(ele, attr_obj, animate_mode = "butter_motion", speed){ // 默認運動方式為緩衝運動 // 遍歷傳入的屬性對象,對每個屬性值進行處理,分別設置屬性的目標值和當前值 for(var attr in attr_obj){ attr_obj[attr] = { // 考慮屬性為「opacity」的特殊情況 target_value : attr === "opacity" ? attr_obj[attr] * 100 : attr_obj[attr], now_value : attr === "opacity" ? parseInt(getComputedStyle(ele)[attr]) * 100 : parseInt(getComputedStyle(ele)[attr]) } } // 定時器都放入ele的對象中,保證每個元素使用自己的定時器互不干擾 clearInterval(ele.timer); ele.timer = setInterval(function(){ // 遍歷傳入的屬性對象,逐個屬性進行運動 for(var attr in attr_obj){ // 勻速運動下的速度設置 if(animate_mode === "uniform_motion"){ // 勻速運動模式可以傳入速度參數,不傳入時默認為5 speed = speed || 5; // 判斷運動方向,即speed的正負 speed = attr_obj[attr].target_value > attr_obj[attr].now_value ? Math.abs(speed) : -Math.abs(speed); } // 緩衝運動下的速度設置 if(animate_mode === "butter_motion"){ // 根據距離目標值的距離不斷調整速度,越靠近目標點速度越慢,且能判斷運動方向 speed = (attr_obj[attr].target_value - attr_obj[attr].now_value) / 10; // 速度的精確處理 speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed) } // 終止條件 if(Math.abs(attr_obj[attr].target_value - attr_obj[attr].now_value) <= Math.abs(speed)){ ele.style[attr] = attr === "opacity" ? attr_obj[attr].target_value / 100 : attr_obj[attr].target_value + "px"; // 目標的不一致會讓運動執行次數不同,有可能提前關閉定時器,故某條屬性運動完成則刪除對象里的屬性數據 delete attr_obj[attr]; // 若對象里還存在屬性,則繼續運動(不關閉定時器) for(var num in attr_obj){ return false; } // 直到對象里沒有屬性,關閉定時器 clearInterval(ele.timer); // 運動條件 }else{ attr_obj[attr].now_value += speed; ele.style[attr] = attr === "opacity" ? attr_obj[attr].target_value / 100 : attr_obj[attr].now_value + "px"; } } }, 30) } var box_ele = document.querySelector(".box"); document.body.onclick = function(){ animate(box_ele, { "width" : 103, "height" : 402, "opacity" : 0.3, "margin-left" : 200 }); } </script> </body> </html>
總結
至此運動函數已封裝完成,該功能適用於大多數情況下元素的運動。
可以實現輪播圖、螢火蟲、放煙花、商品放大鏡等多種效果。