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>
总结
至此运动函数已封装完成,该功能适用于大多数情况下元素的运动。
可以实现轮播图、萤火虫、放烟花、商品放大镜等多种效果。