Javascript之封裝運動函數

  • 2020 年 3 月 30 日
  • 筆記

本文採取逐步深入的方式講解原生JS封裝運動函數的過程,
封裝結果適用於元素大部分屬性的運動,
運動方式將根據需求持續更新,目前主要支持常用的兩種:勻速運動和緩衝運動。

階段一、僅適用單位帶px屬性的勻速運動

效果圖:
在這裡插入圖片描述
封裝思路:

  1. 傳入需要運動的屬性attr、運動的目標值target_value、運動速度speed
  2. 對速度進行簡單處理speed = speed || 5,即不傳入速度參數值時,默認速度為5;
  3. 使用getComputedStyle()獲取元素該屬性的當前值now_value,通過target_value > now_value ? Math.abs(speed) : -Math.abs(speed);獲取運動的方向;
  4. 通過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)的勻速運動

效果圖:
在這裡插入圖片描述
封裝思路:

  1. 在階段一的基礎上添加對元素運動屬性的判定,若運動屬性為opacity,需要對相關值進行處理;
  2. 由於默認速度為5,而opacity的範圍為0~1,故需要對目標值和當前值進行處理,即now_value = parseInt(getComputedStyle(box_ele)[attr] * 100); target_value *= 100;
  3. 渲染元素運動效果時由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>  

階段三、適用於多元素單一屬性的勻速運動

效果圖:
在這裡插入圖片描述
封裝思路:

  1. 在階段二的基礎上添加參數ele,從而可以控制不同元素的運動;
  2. 將定時器放入運動元素對象中,即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>  

階段四、適用於多元素單一屬性的勻速或緩衝運動

效果圖:
在這裡插入圖片描述
封裝思路:

  1. 在階段三的基礎上添加參數animate_mode,並設置animate_mode = "uniform_motion",即默認為勻速運動;
  2. 根據傳入的運動方式計算速度,若傳入的參數為"butter_motion",速度計算方法為speed = (target_value - now_value) / 10,即根據距離目標值的距離不斷調整速度,越靠近目標點速度越慢;
  3. 注意速度計算需要放入定時器中,因為只有定時器中的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>  

階段五、適用於多元素多屬性的勻速或緩衝運動

效果圖:
在這裡插入圖片描述
封裝思路:

  1. 在階段四的基礎上將屬性參數attr、目標值參數target_value刪除,替換為attr_obj
  2. 遍歷傳入的屬性對象,對每個屬性值進行處理,分別設置屬性的目標值target_value和當前值now_value
  3. 在定時器中遍歷傳入的屬性對象,逐個屬性進行運動;
  4. 由於運動目標的不一致會讓運動執行次數不同,有可能提前關閉定時器,故某條屬性運動完成時刪除該條屬性數據,直到對象里沒有屬性,關閉定時器。

完整代碼:

<!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>  

總結

至此運動函數已封裝完成,該功能適用於大多數情況下元素的運動。
可以實現輪播圖、螢火蟲、放煙花、商品放大鏡等多種效果。