Javascript之封装运动函数

本文采取逐步深入的方式讲解原生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>


总结

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

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 228,303评论 6 531
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,478评论 3 415
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 176,230评论 0 373
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,936评论 1 309
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,688评论 6 409
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,174评论 1 323
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,243评论 3 441
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,402评论 0 288
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,932评论 1 334
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,771评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,971评论 1 369
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,514评论 5 359
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,209评论 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,631评论 0 26
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,863评论 1 283
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,640评论 3 391
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,949评论 2 373

推荐阅读更多精彩内容