JavaScript 基础入门11 - 运动框架的封装

目录

  • JavaScript 运动原理

    • 运动基础
    • 简单运动的封装
    • 淡入淡出
    • 不同属性的设置
    • 多属性值同时运动
    • 运动回调,链式运动
    • 缓冲运动
    • 加入缓冲的运动框架

JavaScript 运动原理

运动基础

  1. 在JavaScript中,如何让一个页面元素动起来?
    首先,我们需要了解的是,在JavaScript中如何让一个页面元素动起来。

我们先来实现一个简单的功能,当我们点击按钮之后,让一个元素动起来。并且到达500的边界之后立刻停止下来。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <style>
            #d1 {
                width: 100px;
                height: 100px;
                background-color: red;
                position: absolute;
                top:100px;
                left: 200px;
            }
        </style>
    </head>
    <body>
        <button id="btn">点击运动</button>
        <div id="d1"></div>
    </body>
    <script>
        // 点击按钮,让div横向的运动起来

        // 1. 获取元素
        let oBtn = document.getElementById('btn');
        let oDiv = document.getElementById('d1');
        let iTimer = null;
        // 点击按钮,让元素一直运动 ,需要使用到的知识点:定时器
        oBtn.onclick = ()=>{

            iTimer = setInterval(()=>{
                // 点击按钮之后,让div的位置在当前的基础之上每次增加10px的距离
                // oDiv.style.left = oDiv.offsetLeft + 10 + 'px';  // 虽然此代码可以让div动起来,但是我们需要div在运动之后到达某个边界就立刻停止,所以需要将此句代码改为一个判断
                if (oDiv.offsetLeft === 500) {
                    // 清除定时器
                    clearInterval(iTimer);
                }else { // 没有到达边界才能继续运动
                    oDiv.style.left = oDiv.offsetLeft + 10 + 'px';
                }
            },30);

        };
    </script>
</html>

在上面的代码中,我们点击按钮之后,元素已经可以直接进行移动,但是却存在一个问题,什么问题呢?

当我们点击按钮之后,元素始终以10px的匀速进行运动,到达500的临界然后停止。 但是我们的问题是,速度可能会变,例如将速度变为7px,就不能够
准确的到达500的临界值。

例如:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <style>
            #d1 {
                width: 100px;
                height: 100px;
                background-color: red;
                position: absolute;
                top:100px;
                left: 200px;
            }
        </style>
    </head>
    <body>
        <button id="btn">点击运动</button>
        <div id="d1"></div>
    </body>
    <script>
        // 点击按钮,让div横向的运动起来

        // 1. 获取元素
        let oBtn = document.getElementById('btn');
        let oDiv = document.getElementById('d1');
        let iTimer = null;
        // 点击按钮,让元素一直运动 ,需要使用到的知识点:定时器
        oBtn.onclick = ()=>{

            iTimer = setInterval(()=>{
                // 点击按钮之后,让div的位置在当前的基础之上每次增加10px的距离
                // oDiv.style.left = oDiv.offsetLeft + 10 + 'px';  // 虽然此代码可以让div动起来,但是我们需要div在运动之后到达某个边界就立刻停止,所以需要将此句代码改为一个判断
                if (oDiv.offsetLeft === 500) {
                    // 清除定时器
                    clearInterval(iTimer);
                }else { // 没有到达边界才能继续运动
                    oDiv.style.left = oDiv.offsetLeft + 7 + 'px';  // 一旦将每次的移动距离变为7px ,那么将不能停的下来.元素会一直运动
                }
            },30);

        };
    </script>
</html>

出现这种情况的原因是因为运动的临界值必须能够被运动的速度(也就是oDiv.offsetLeft + 7 + ‘px‘,表示每次执行移动的距离)整除。

上面的代码当中, 因为临界值不能够被速度整除,所以,最终元素始终达到不了临界值,那么元素就没有办法在到达临界值时停止。

同时在上面的代码中的另外一个问题是,当我们每点击一次运动按钮,元素的速度就会变得更快,原因很简单,就是我们设置的定时器发生了累加。

那么该如何解决定时器累加的问题呢?

我们可以在每次开始运动之前先清楚一次定时器。

oBtn.onclick = ()=>{
            /*
            * 为了防止定时器累加,在每次开始定时器之前,先清楚掉一个定时器
            * */
            clearInterval(iTimer);

            iTimer = setInterval(()=>{
                // 点击按钮之后,让div的位置在当前的基础之上每次增加10px的距离
                // oDiv.style.left = oDiv.offsetLeft + 10 + 'px';  // 虽然此代码可以让div动起来,但是我们需要div在运动之后到达某个边界就立刻停止,所以需要将此句代码改为一个判断
                if (oDiv.offsetLeft === 500) {
                    // 清除定时器
                    clearInterval(iTimer);
                }else { // 没有到达边界才能继续运动
                    oDiv.style.left = oDiv.offsetLeft + 7 + 'px';  // 一旦将每次的移动距离变为7px ,那么将不能停的下来.元素会一直运动
                }
            },30);
        };

总结:在上面的代码中,是我们一般让一个元素运动起来的基本流程。下面进行一个简单的总结:

  1. 首先是存在的问题:处于匀速运动的元素没有办法进行在不整除的情况下在临界点停止。
  2. 在上面的代码中,可以将整个过程大致分为三个步骤:
    • 清除定时器,保证只有一个定时器在执行
    • 开启定时器
    • 开始运动(需要同时加入一个判断,以便在需要的时候或者满足某个要求时停止运动)

简单运动的封装

为了让我们上面的代码可以具备更高的复用性,下面我们把上面的代码进行一个简单的封装。

示例:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <style>
            #d1 {
                width: 100px;
                height: 100px;
                background-color: red;
                position: absolute;
                top:100px;
                left: 200px;
            }
        </style>
    </head>
    <body>
        <button id="btn">点击运动</button>
        <div id="d1"></div>
    </body>
    <script>
        // 点击按钮,让div横向的运动起来

        // 1. 获取元素
        let oBtn = document.getElementById('btn');
        let oDiv = document.getElementById('d1');
        let iTimer = null;
        // 点击按钮,让元素一直运动 ,需要使用到的知识点:定时器
        oBtn.onclick = ()=>{
            startMove();
            // 将运动相关的内容全部放到startMove这个函数中,然后调用既可以让元素进行运动
            function startMove() {
                clearInterval(iTimer);
                iTimer = setInterval(()=>{
                    if (oDiv.offsetLeft === 500) {
                        // 清除定时器
                        clearInterval(iTimer);
                    }else { // 没有到达边界才能继续运动
                        oDiv.style.left = oDiv.offsetLeft + 10 + 'px';  // 一旦将每次的移动距离变为7px ,那么将不能停的下来.元素会一直运动
                    }
                },30);
            }
        };
    </script>
</html>

在上面的代码中,我们将运动相关的内容放到了一个函数startMove中,并且调用了这个函数,下面来根据这个函数进行案例的开发。

案例1:分享到功能

首先,先来实现基本的功能:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <style>
            #div1 {
                width: 100px;
                height: 200px;
                background:red;
                position: absolute;
                left: -100px;
                top: 200px;
            }
            #div2 {
                width: 30px;
                height: 70px;
                background: black;
                position:absolute;
                right:-30px;
                top: 70px;;
                color:#fff;
                text-align: center;
            }
        </style>
    </head>
    <body>
        <div id="div1">
            <div id="div2">分享到</div>
        </div>
    </body>
    <script>
        // 1. 首先获取两个元素
        let oDiv1 = document.getElementById('div1');
        let oDiv2 = document.getElementById('div2');
        let iTimer = null;  

        // 给为父级的div绑定mouseover 和 mouseout事件
        oDiv1.onmouseover = function() {
            this.style.left = 0 + 'px'; // 鼠标移入,让div元素出现
        };

        oDiv1.onmouseout = function() {
            this.style.left = -100 + 'px'; // 鼠标移出,让div隐藏
        }; 

    </script>
</html>

上面的代码中,我们鼠标移入,元素出现。鼠标移出,元素消失。
下面我们来使用我们的startMove函数,给元素出现和消失加上一个过渡的效果。

我们的startMove函数如下:

function startMove() {
    clearInterval(iTimer);
    iTimer = setInterval(()=>{
        if (oDiv.offsetLeft === 500) {
            // 清除定时器
            clearInterval(iTimer);
        }else { // 没有到达边界才能继续运动
            oDiv.style.left = oDiv.offsetLeft + 10 + 'px';  // 一旦将每次的移动距离变为7px ,那么将不能停的下来.元素会一直运动
        }
    },30);
}

我们想要在分享到功能里使用这个函数,我们需要对我们的函数根据分享到的需求进行一定程度的更改。
首先是,将我们函数中的oDvi更改为oDiv1

其次是我们分享到案例的需求在鼠标移入时需要将元素逐渐的显示,而鼠标移出时,需要将元素逐渐的隐藏。所以我们需要将startMove函数创建两个
,并且当鼠标移出时,速度应该将函数中的+10变为-10

当然,也别忘了去更该元素边界的值。当移出时,边界为0,移入时,边界为-100.

如下:

鼠标移入时调用的startMove1函数:

function startMove1() {
    clearInterval(iTimer);
    iTimer = setInterval(()=>{
        if (oDiv1.offsetLeft === 0) {
            // 清除定时器
            clearInterval(iTimer);
        }else { // 没有到达边界才能继续运动
            oDiv1.style.left = oDiv.offsetLeft + 10 + 'px';  // 一旦将每次的移动距离变为7px ,那么将不能停的下来.元素会一直运动
        }
    },30);
}

鼠标移出时调用的startMove2函数:

function startMove() {
    clearInterval(iTimer);
    iTimer = setInterval(()=>{
        if (oDiv1.offsetLeft === -100) {
            // 清除定时器
            clearInterval(iTimer);
        }else { // 没有到达边界才能继续运动
            oDiv1.style.left = oDiv.offsetLeft - 10 + 'px';  // 一旦将每次的移动距离变为7px ,那么将不能停的下来.元素会一直运动
        }
    },30);
}

我们先来将这两个函数放在代码中,进行测试。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <style>
            #div1 {
                width: 100px;
                height: 200px;
                background:red;
                position: absolute;
                left: -100px;
                top: 200px;
            }
            #div2 {
                width: 30px;
                height: 70px;
                background: black;
                position:absolute;
                right:-30px;
                top: 70px;;
                color:#fff;
                text-align: center;
            }
        </style>
    </head>
    <body>
        <div id="div1">
            <div id="div2">分享到</div>
        </div>
    </body>
    <script>
        // 1. 首先获取两个元素
        let oDiv1 = document.getElementById('div1');
        let oDiv2 = document.getElementById('div2');
        let iTimer = null;  

        // 给为父级的div绑定mouseover 和 mouseout事件
        oDiv1.onmouseover = function() {
            // this.style.left = 0 + 'px'; // 鼠标移入,让div元素出现
            startMove1();
        };

        oDiv1.onmouseout = function() {
            // this.style.left = -100 + 'px'; // 鼠标移出,让div隐藏
            startMove2();
        }; 

        function startMove1() {
            clearInterval(iTimer);
            iTimer = setInterval(()=>{
                if (oDiv1.offsetLeft === 0) {
                    // 清除定时器
                    clearInterval(iTimer);
                }else { // 没有到达边界才能继续运动
                    oDiv1.style.left = oDiv1.offsetLeft + 10 + 'px';  // 一旦将每次的移动距离变为7px ,那么将不能停的下来.元素会一直运动
                }
            },30);
        }
        function startMove2() {
            clearInterval(iTimer);
            iTimer = setInterval(()=>{
                if (oDiv1.offsetLeft === -100) {
                    // 清除定时器
                    clearInterval(iTimer);
                }else { // 没有到达边界才能继续运动
                    oDiv1.style.left = oDiv1.offsetLeft - 10 + 'px';  // 一旦将每次的移动距离变为7px ,那么将不能停的下来.元素会一直运动
                }
            },30);
        }
    </script>
</html>

上面的代码中,我们通过创建两个startMove函数,并且对相应的参数进行修改,从而实现了给我们的分享到功能添加了过渡效果。

进一步升级:
当然,我们上面的代码中使用的函数其实是非常不灵活的,所以我们下面要做到事就是对之前的函数进行升级,从而让我们的函数具备更强的实用性。

首先我们再回过头来看下我们刚才写的两个函数,你会发现,大部分的代码其实都是相同的,只有个别的值是不同的,例如元素移动的边界,例如元素
单位时间内移动的距离。

我们将上面的两个函数合并成一个函数,只需要将不一样的值提取出来当做参数即可。

下面是合并之后的函数:

function startMove(iTarget,iSpeed) {
    clearInterval(iTimer);
    iTimer = setInterval(()=>{
        if (oDiv1.offsetLeft === iTarget) {
            // 清除定时器
            clearInterval(iTimer);
        }else { // 没有到达边界才能继续运动
            oDiv1.style.left = oDiv1.offsetLeft +  iSpeed + 'px';
        }
    },30);
}

上面的函数升级完成之后,我们在重新的将这个函数应用到我们的分享到功能的代码当中去。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <style>
            #div1 {
                width: 100px;
                height: 200px;
                background:red;
                position: absolute;
                left: -100px;
                top: 200px;
            }
            #div2 {
                width: 30px;
                height: 70px;
                background: black;
                position:absolute;
                right:-30px;
                top: 70px;;
                color:#fff;
                text-align: center;
            }
        </style>
    </head>
    <body>
        <div id="div1">
            <div id="div2">分享到</div>
        </div>
    </body>
    <script>
        // 1. 首先获取两个元素
        let oDiv1 = document.getElementById('div1');
        let oDiv2 = document.getElementById('div2');
        let iTimer = null;  

        // 给为父级的div绑定mouseover 和 mouseout事件
        oDiv1.onmouseover = function() {
            // this.style.left = 0 + 'px'; // 鼠标移入,让div元素出现
            startMove(0,10);
        };

        oDiv1.onmouseout = function() {
            // this.style.left = -100 + 'px'; // 鼠标移出,让div隐藏
            startMove(-100,-10);
        }; 

        function startMove(iTarget,iSpeed) {
            clearInterval(iTimer);
            iTimer = setInterval(()=>{
                if (oDiv1.offsetLeft === iTarget) {
                    // 清除定时器
                    clearInterval(iTimer);
                }else { // 没有到达边界才能继续运动
                    oDiv1.style.left = oDiv1.offsetLeft +  iSpeed + 'px';
                }
            },30);
        }
    </script>
</html>

上面的代码中,我们顺利的通过我们的startMove函数给分享到功能添加了过渡的效果。

图片的淡入淡出效果:

下面我们再来看另外的一个效果,图片的淡入淡出,还是通过我们上面定义好的startMove函数来实现效果。

首先,我们上面的函数当中,只是针对oDiv1,为了让我们的函数可以处理任意的元素,我们将oDiv1替换成函数的形参。

例如:

function startMove(oDom,iTarget,iSpeed) {
    clearInterval(iTimer);
    iTimer = setInterval(()=>{
        if (oDom.offsetLeft === iTarget) {
            // 清除定时器
            clearInterval(iTimer);
        }else { // 没有到达边界才能继续运动
            oDom.style.left = oDom.offsetLeft +  iSpeed + 'px';
        }
    },30);
}

上面的代码当中,我们将函数操作的元素提取出来,变成了函数的形参,这样做之后,我们就可以让我们的函数针对任意的元素。

淡入淡出

下面的任务就是让我们的函数来处理图片的淡入淡出问题。

首先我们先来看下js当中的一个普遍情况,就是精度问题。

JavaScript精度问题
在js当中,关于小数的运算一向都是不够准确的。

例如:

alert(0.2 + 0.1);// 预期值 0.4  实际值: 0.30000000000000004
alert(0.2 + 0.7);// 预期值: 0.9 实际值: 0.8999999999999999

在上面的代码中,我们可以发现,在js当中的小数运算,精度是存在问题的,并不是很精确。

为什么要说到透明度的问题呢?

因为我们在设置透明度(opacity)的时候,还要考虑到兼容性的问题,另外一种写法(ie)是filter:alpha(opacity=30),里面的具体的值是正常的
透明度100,例如,我们正常设置透明度时,值为0.3,那么filter里面的值就是30。而因为JavaScript中的小数计算不够精准,所以我们在
后面的函数的封装中将采用整数的形式,也就是正常的透明度小数值
100的结果。

知道了上面的内容,我们来正式的写一下代码,首先先来引入一张图片:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <style>
            #img1 {
                width: 200px;
                height: 200px;
                opacity: 0.3;
                filter:alpha(opacity=30); /*为了兼容ie*/
                margin-left:300px;
            }
        </style>
    </head>
    <body>
        <img src="./01.jpg" id="img1">
    </body>
    <script>
        // 获取元素 并且绑定事件 和 设置事件处理函数
        let oImg1 = document.getElementById('img1');

        oImg1.onmouseover = function() {
            // 调用鼠标移入处理的函数
        };

        oImg1.onmouseout = function () {
            // 调用鼠标移出处理的函数
        };

    </script>
</html>

在上面的代码中,我们已经找到图片元素,并且给图片元素绑定了事件以及事件处理函数,下面我们再来更改一下我们之前的startMove函数,让它专门
能够针对当前案例。

function startMove(oDom,iTarget,iSpeed) {
    let iTimer = null;
    clearInterval(iTimer);
    let iCur = 0; // 用这个变量来存储透明度处理的值
    iTimer = setInterval(()=>{
        // 为了让值更加的精确,将结果四舍五入
        iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100
        if (iCur === iTarget) { // 判断是否等于目标的透明度

            clearInterval(iTimer);
        }else {
            // 如果没有到达目标值 就让元素的透明度发生变化,需要分别设置ie和非ie
            oDom.style.opacity = (iCur + iSpeed) / 100;
            oDom.style.filter = 'alpha(opacity='+  (iCur + iSpeed) +')';
        }
    },30);
}

在上面的代码中,我们为了获取图片本身的透明度,我们使用了一个css函数,这是一个我们自定义的函数,目的是为了获取元素的属性。

例如:

/**
 * css 函数,可以用来获取元素的css属性
 * 可以兼容ie和普通的浏览器
 * */
function css(obj,attr) {
    if (obj.currentStyle) {
        return obj.currentStyle[attr];
    }else {
        return getComputedStyle(obj,false)[attr];
    }
}

我们将startMove函数和css函数应用到上面的img案例当中,整体代码如下:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <style>
            #img1 {
                width: 200px;
                height: 200px;
                opacity: 0.3;
                filter:alpha(opacity=30); /*为了兼容ie*/
                margin-left:300px;
            }
        </style>
    </head>
    <body>
        <img src="./01.jpg" id="img1">
    </body>
    <script>
        // 获取元素 并且绑定事件 和 设置事件处理函数
        let oImg1 = document.getElementById('img1');

        oImg1.onmouseover = function() {
            // 调用鼠标移入处理的函数
            startMove(this,100,10); // this 向当前的图片  100 为最终的透明度 10 为单位变化的值
        };

        oImg1.onmouseout = function () {
            // 调用鼠标移出处理的函数
            startMove(this,30,-10);
        };

        function startMove(oDom,iTarget,iSpeed) {
            let iTimer = null;
            clearInterval(iTimer);
            let iCur = 0; // 用这个变量来存储透明度处理的值
            iTimer = setInterval(()=>{
                // 为了让值更加的精确,将结果四舍五入
                iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100
                if (iCur === iTarget) { // 判断是否等于目标的透明度

                    clearInterval(iTimer);
                }else {
                    // 如果没有到达目标值 就让元素的透明度发生变化,需要分别设置ie和非ie
                    oDom.style.opacity = (iCur + iSpeed) / 100;
                    oDom.style.filter = 'alpha(opacity='+  (iCur + iSpeed) +')';
                }
            },30);
        }
        /**
         * css 函数,可以用来获取元素的css属性
         * 可以兼容ie和普通的浏览器
         * */
        function css(obj,attr) {
            if (obj.currentStyle) {
                return obj.currentStyle[attr];
            }else {
                return getComputedStyle(obj,false)[attr];
            }
        }
    </script>
</html>

上面的代码中,我们通过更改的startMove函数和css函数实现了我们的需求,图片淡入淡出。

不同属性的设置

函数再升级:针对元素的不同属性效果

上面我们实现了分享到和图片的淡入淡出功能,我们为了实现功能,对我们的函数进行更改。而如果一个网页中同时存在着分享到和图片淡入淡出,或者
说在一个网页中同时存在需要运动的元素和需要淡入淡出的元素,根据我们上面的模式,只能在网页中同时设置两个函数:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <style>
            #div1 {
                width: 100px;
                height: 200px;
                background:red;
                position: absolute;
                left: -100px;
                top: 200px;
            }
            #div2 {
                width: 30px;
                height: 70px;
                background: black;
                position:absolute;
                right:-30px;
                top: 70px;;
                color:#fff;
                text-align: center;
            }
            #img1 {
                width: 200px;
                height: 200px;
                opacity: 0.3;
                filter:alpha(opacity=30); /*为了兼容ie*/
                margin-left:300px;
            }
        </style>
    </head>
    <body>
        <div id="div1">
            <div id="div2">分享到</div>
        </div>

        <img src="./01.jpg" id="img1">
    </body>
    <script>
        // 获取元素 并且绑定事件 和 设置事件处理函数
        let oImg1 = document.getElementById('img1');

        oImg1.onmouseover = function() {
            // 调用鼠标移入处理的函数
            startMove1(this,100,10); // this 向当前的图片  100 为最终的透明度 10 为单位变化的值
        };

        oImg1.onmouseout = function () {
            // 调用鼠标移出处理的函数
            startMove1(this,30,-10);
        };

        function startMove1(oDom,iTarget,iSpeed) {
            let iTimer = null;
            clearInterval(iTimer);
            let iCur = 0; // 用这个变量来存储透明度处理的值
            iTimer = setInterval(()=>{
                // 为了让值更加的精确,将结果四舍五入
                iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100
                if (iCur === iTarget) { // 判断是否等于目标的透明度

                    clearInterval(iTimer);
                }else {
                    // 如果没有到达目标值 就让元素的透明度发生变化,需要分别设置ie和非ie
                    oDom.style.opacity = (iCur + iSpeed) / 100;
                    oDom.style.filter = 'alpha(opacity='+  (iCur + iSpeed) +')';
                }
            },30);
        }
        /**
         * css 函数,可以用来获取元素的css属性
         * 可以兼容ie和普通的浏览器
         * */
        function css(obj,attr) {
            if (obj.currentStyle) {
                return obj.currentStyle[attr];
            }else {
                return getComputedStyle(obj,false)[attr];
            }
        }

        // 1. 首先获取两个元素
        let oDiv1 = document.getElementById('div1');
        let oDiv2 = document.getElementById('div2');
        let iTimer = null;  

        // 给为父级的div绑定mouseover 和 mouseout事件
        oDiv1.onmouseover = function() {
            // this.style.left = 0 + 'px'; // 鼠标移入,让div元素出现
            startMove2(0,10);
        };

        oDiv1.onmouseout = function() {
            // this.style.left = -100 + 'px'; // 鼠标移出,让div隐藏
            startMove2(-100,-10);
        }; 

        function startMove2(iTarget,iSpeed) {
            clearInterval(iTimer);
            iTimer = setInterval(()=>{
                if (oDiv1.offsetLeft === iTarget) {
                    // 清除定时器
                    clearInterval(iTimer);
                }else { // 没有到达边界才能继续运动
                    oDiv1.style.left = oDiv1.offsetLeft +  iSpeed + 'px';
                }
            },30);
        }
    </script>
</html>

我们发现,在上面的代码中,网页里同时存在分享到和图片淡入淡出的两个功能。

而且我们发现startMove1和startMove2两个函数的相似代码度非常高,那么我们是否可以将两个函数进行合并呢?

合并函数的第一步,找到两个函数当中不同的地方,然后将其替换成函数的参数。

运动属性的不同:
经过比对之后,我们可以发现,在上面的两个函数当中,存在一个不同之处,就是运动属性的不同,所以,我们可以再原本函数的基础上再来提取一个
运动属性

那么更改之后的函数的调用方式就变成了类似下面这种:

// 让元素移动
startMove(this,'left',0,10); // this 运动的元素  left 运动的属性  0 目标  10 速度
// 让元素透明度发生变化
startMove(this,'opacity',30,-10); // this 变化的元素  opacity 设置的属性  30 目标 -10 单位变化的值 

当我们弄清楚了函数的调用方式之后,就可以修改我们的函数了。

首先,可以先将我们之前的iTimer变量设置到oDom这个对象身上。

function startMove(oDom,attr,iTarget,iSpeed) {
    clearInterval(oDom.iTimer);
    // ....
    oDom.iTimer = setInterval(()=>{
        // code ...
    };
};

我们接下来需要对设置的属性做一下判断,在css当中,透明度的设置需要特殊处理,其他的属性正常设置值就好。

oDom.iTimer = setInterval(()=>{
    // 进行操作判断
    if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理
        iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100
    }else { // 其他的值可以直接设置
        iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数
    }
};

当我们对属性做完初步的判断之后,还需要对赋值进行更具体的判断,代码如下:

if (iCur === iTarget) { // 判断当前值是否等于目标值,
    clearInterval(iTimer); // 如果等于,则清除定时
}else { // 如果不等于,就再来具体判断
    if(attr === 'opacity'){ // 对透明度的设置需要特殊处理
        oDom.style.opacity = (iCur + iSpeed) / 100;
        oDom.style.filter = 'alpha(opacity='+  (iCur + iSpeed) +')';
    }else { // 其他属性值的设置
        oDom.style[attr] = iCur + iSpeed + 'px';
    }

}

完整的函数如下:

/*
        * oDOM 要操作的元素
        * attr 要操作的属性
        * iTarget 变化的目标值
        * iSpeed 变化的速度
        */
        function startMove(oDom,attr,iTarget,iSpeed) { 

            clearInterval(oDom.iTimer);
            let iCur = 0; // 用这个变量来存储透明度处理的值
            oDom.iTimer = setInterval(()=>{

                // 进行操作判断
                if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理
                    iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100
                }else { // 其他的值可以直接设置
                    iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数
                }

                if (iCur === iTarget) { // 判断是否等于目标的透明度
                    clearInterval(oDom.iTimer);
                }else {
                    if(attr === 'opacity'){ // 对透明度的设置需要特殊处理
                        oDom.style.opacity = (iCur + iSpeed) / 100;
                        oDom.style.filter = 'alpha(opacity='+  (iCur + iSpeed) +')';
                    }else { // 其他属性值的设置
                        oDom.style[attr] = iCur + iSpeed + 'px';
                    }

                }
            },30);
        }
        /**
         * css 函数,可以用来获取元素的css属性
         * 可以兼容ie和普通的浏览器
         * */
        function css(obj,attr) {
            if (obj.currentStyle) {
                return obj.currentStyle[attr];
            }else {
                return getComputedStyle(obj,false)[attr];
            }
        }

将这个函数应用到网页当中,分享到和图片的淡入淡出效果都可以直接调用。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <style>
            #div1 {
                width: 100px;
                height: 200px;
                background:red;
                position: absolute;
                left: -100px;
                top: 200px;
            }
            #div2 {
                width: 30px;
                height: 70px;
                background: black;
                position:absolute;
                right:-30px;
                top: 70px;;
                color:#fff;
                text-align: center;
            }
            #img1 {
                width: 200px;
                height: 200px;
                opacity: 0.3;
                filter:alpha(opacity=30); /*为了兼容ie*/
                margin-left:300px;
            }
        </style>
    </head>
    <body>
        <div id="div1">
            <div id="div2">分享到</div>
        </div>

        <img src="./01.jpg" id="img1">
    </body>
    <script>
        // 获取元素 并且绑定事件 和 设置事件处理函数
        let oImg1 = document.getElementById('img1');

        oImg1.onmouseover = function() {
            // 调用鼠标移入处理的函数
            startMove(this,'opacity',100,10); // this 向当前的图片  100 为最终的透明度 10 为单位变化的值
        };

        oImg1.onmouseout = function () {
            // 调用鼠标移出处理的函数
            startMove(this,'opacity',30,-10);
        };
        /*
        * oDOM 要操作的元素
        * attr 要操作的属性
        * iTarget 变化的目标值
        * iSpeed 变化的速度
        */
        function startMove(oDom,attr,iTarget,iSpeed) { 

            clearInterval(oDom.iTimer);
            let iCur = 0; // 用这个变量来存储透明度处理的值
            oDom.iTimer = setInterval(()=>{

                // 进行操作判断
                if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理
                    iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100
                }else { // 其他的值可以直接设置
                    iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数
                }

                if (iCur === iTarget) { // 判断是否等于目标的透明度
                    clearInterval(oDom.iTimer);
                }else {
                    if(attr === 'opacity'){ // 对透明度的设置需要特殊处理
                        oDom.style.opacity = (iCur + iSpeed) / 100;
                        oDom.style.filter = 'alpha(opacity='+  (iCur + iSpeed) +')';
                    }else { // 其他属性值的设置
                        oDom.style[attr] = iCur + iSpeed + 'px';
                    }

                }
            },30);
        }
        /**
         * css 函数,可以用来获取元素的css属性
         * 可以兼容ie和普通的浏览器
         * */
        function css(obj,attr) {
            if (obj.currentStyle) {
                return obj.currentStyle[attr];
            }else {
                return getComputedStyle(obj,false)[attr];
            }
        }

        // 1. 首先获取两个元素
        let oDiv1 = document.getElementById('div1');
        let oDiv2 = document.getElementById('div2');
        let iTimer = null;  

        // 给为父级的div绑定mouseover 和 mouseout事件
        oDiv1.onmouseover = function() {
            // this.style.left = 0 + 'px'; // 鼠标移入,让div元素出现
            startMove(this,'left',0,10);
        };

        oDiv1.onmouseout = function() {
            // this.style.left = -100 + 'px'; // 鼠标移出,让div隐藏
            startMove(this,'left',-100,-10);
        }; 

    </script>
</html>

在上面的案例当中,我们已经能够实现一个页面当中多个元素同时调用一个函数进行运动。

下面我们再来将我们的函数进行升级,升级成,一个元素可以通过函数进行多值同时运动。

多属性值同时运动

多值同时运动:

例如,在网页当中存在一个div,然后当我们点击这个div的时候,让宽度变为200px,高度也变为200px。

首先,我们先来完成页面的基本布局:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <style>
            #div1 {
                width: 100px;
                height: 100px;
                background-color: red;
                position: absolute;
                top: 100px;
                left: 200px;
            }
        </style>
    </head>
    <body>
        <div id="div1"></div>
    </body>
</html>

设置完成基本样式之后,我们再来把基本的js代码实现:

let oDiv1 = document.getElementById('div1');

oDiv1.onclick = function(){

};

function startMove(oDom,attr,iTarget,iSpeed) { 

    clearInterval(oDom.iTimer);
    let iCur = 0; // 用这个变量来存储透明度处理的值
    oDom.iTimer = setInterval(()=>{

        // 进行操作判断
        if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理
            iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100
        }else { // 其他的值可以直接设置
            iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数
        }

        if (iCur === iTarget) { // 判断是否等于目标的透明度
            clearInterval(oDom.iTimer);
        }else {
            if(attr === 'opacity'){ // 对透明度的设置需要特殊处理
                oDom.style.opacity = (iCur + iSpeed) / 100;
                oDom.style.filter = 'alpha(opacity='+  (iCur + iSpeed) +')';
            }else { // 其他属性值的设置
                oDom.style[attr] = iCur + iSpeed + 'px';
            }

        }
    },30);
}

在上面的代码中,我们给div元素绑定了单击事件,下面我们再来处理一下moveStart函数。

为了能够一次性操作多个值,我们可以将操作的属性以json的形式来传入。

// 以下面的方式进行传值
startMove(this,{ // this 操作的元素
    width:200, // 操作的属性1
    height:200 // 操作的属性2
},10); // 速度  

为了实现上面的传递值的方式,函数的参数需要变成下面的样式:

function startMove(oDom,json,iSpeed){
    // code ...
}

在更改具体的代码之前,我们先来回顾之前的代码
我们之前的代码在设置属性时,采用的是下面的写法:

oDom.iTimer = setInterval(()=>{

    // 进行操作判断
    if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理
        iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100
    }else { // 其他的值可以直接设置
        iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数
    }
    // code ...
},30);

上面的代码是我们之前的代码,但是为了实现多值同时变化,需要将上面的判断代码更改成如下的样子:

function startMove(oDom,json,iSpeed) { 

    clearInterval(oDom.iTimer);
    let iCur = 0; // 用这个变量来存储透明度处理的值
    oDom.iTimer = setInterval(()=>{

        // 因为传入的参数是json,为了逐一操作,需要开启循环
        for(let attr in json) {
            // 设置目标值
            let iTarget = json[attr]; // 当前属性要运动的值就是目标值
            // 进行操作判断
            if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理
                iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100
            }else { // 其他的值可以直接设置
                iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数
            }

            if (iCur === iTarget) { // 判断是否等于目标的透明度
                clearInterval(oDom.iTimer);
            }else {
                if(attr === 'opacity'){ // 对透明度的设置需要特殊处理
                    oDom.style.opacity = (iCur + iSpeed) / 100;
                    oDom.style.filter = 'alpha(opacity='+  (iCur + iSpeed) +')';
                }else { // 其他属性值的设置
                    oDom.style[attr] = iCur + iSpeed + 'px';
                }
            }
        }
    },30);
    }

    function css(obj,attr) {
        if (obj.currentStyle) {
            return obj.currentStyle[attr];
        }else {
            return getComputedStyle(obj,false)[attr];
        }
    }

将更改之后的函数放在单击事件处理函数里执行。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <style>
            #div1 {
                width: 100px;
                height: 100px;
                background-color: red;
                position: absolute;
                top: 100px;
                left: 200px;
            }
        </style>
    </head>
    <body>
        <div id="div1"></div>
    </body>
    <script>
        let oDiv1 = document.getElementById('div1');

        oDiv1.onclick = function(){
            startMove(this,{
                width:200, // 不能加px
                height:200
            },10);
        };

        function startMove(oDom,json,iSpeed) { 

            clearInterval(oDom.iTimer);
            let iCur = 0; // 用这个变量来存储透明度处理的值
            oDom.iTimer = setInterval(()=>{

                // 因为传入的参数是json,为了逐一操作,需要开启循环
                for(let attr in json) {
                    // 设置目标值
                    let iTarget = json[attr]; // 当前属性要运动的值就是目标值
                    // 进行操作判断
                    if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理
                        iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100
                    }else { // 其他的值可以直接设置
                        iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数
                    }

                    if (iCur === iTarget) { // 判断是否等于目标的透明度
                        clearInterval(oDom.iTimer);
                    }else {
                        if(attr === 'opacity'){ // 对透明度的设置需要特殊处理
                            oDom.style.opacity = (iCur + iSpeed) / 100;
                            oDom.style.filter = 'alpha(opacity='+  (iCur + iSpeed) +')';
                        }else { // 其他属性值的设置
                            oDom.style[attr] = iCur + iSpeed + 'px';
                        }
                    }
                }
            },30);
        }

        function css(obj,attr) {
            if (obj.currentStyle) {
                return obj.currentStyle[attr];
            }else {
                return getComputedStyle(obj,false)[attr];
            }
        }

    </script>
</html>

我们上面的代码运行经过测试之后,点击div,已经可以实现宽度和高度同时变化。

但是我们上面的代码却存在一个问题。

例如,将宽度变为200,高度变为300.

startMove(this,{width:200,height:300},10);

这个时候,我们发现点击元素之后,宽度变为了200,但是高度却没有变成指定的300。原因也很简单,多个变化属性的速度是一致的,当其中一个属性
到达目标值时,定时器就会被清除,这个时候个别属性就不再发生变化。

所以这个时候我们就要思考一个问题,到底什么时候停止定时器?

答案其实很简单,就是当所有属性全部达到目标值时才停止运动。

我们再回来分析一下之前的代码,我们每一次循环,其实都相当于变化了一个属性,在循环的过程中,其中一个属性变化的过程中并没有办法知道另外一个
属性是否完成了变化。

所以我们应该在循环之外判断,当循环一轮之后,我们就立刻在循环之外看下每个属性是否到达了目标值。

首先,我们在代码之前设置一个变量,存储一个布尔值,用来表示是否达到目标点。
在每次进入定时器时,都把属性变为true,然后在后续的判断中,如果属性没到达目标点,就把这个变量的值变为false。

oDom.iTimer = setInterval(()=>{
    // 设置变量存储初始值
    let iBtn = true;

    // code ...
    for(let attr in json) {
        // code ...
        // 将原来的iCur === iTarget 变为下面的判断
        if (iCur !== iTarget) {
            // 如果其中的一个属性没到,就将iBtn 变为false
            iBtn = false;
            if(attr === 'opacity'){ // 对透明度的设置需要特殊处理
                oDom.style.opacity = (iCur + iSpeed) / 100;
                oDom.style.filter = 'alpha(opacity='+  (iCur + iSpeed) +')';
            }else { // 其他属性值的设置
                oDom.style[attr] = iCur + iSpeed + 'px';
            }
        }
    }

    // 通过判断iBtn的值来决定清除定时器
    if (iBtn){
        clearInterval(oDom.iTimer);
    }
},30}

完整的函数代码如下:

function startMove(oDom,json,iSpeed) { 

    clearInterval(oDom.iTimer);
    let iCur = 0; // 用这个变量来存储透明度处理的值
    oDom.iTimer = setInterval(()=>{

        // 设置一个变量,用来存储状态,表示元素是否到达目标值
        let iBtn = true;

        // 因为传入的参数是json,为了逐一操作,需要开启循环
        for(let attr in json) {
            // 设置目标值
            let iTarget = json[attr]; // 当前属性要运动的值就是目标值
            // 进行操作判断
            if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理
                iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100
            }else { // 其他的值可以直接设置
                iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数
            }

            if (iCur !== iTarget) { // 判断是否等于目标的透明度
                iBtn = false;
                if(attr === 'opacity'){ // 对透明度的设置需要特殊处理
                    oDom.style.opacity = (iCur + iSpeed) / 100;
                    oDom.style.filter = 'alpha(opacity='+  (iCur + iSpeed) +')';
                }else { // 其他属性值的设置
                    oDom.style[attr] = iCur + iSpeed + 'px';
                }
            }
        }   

        // 判断iBtn是否为true,为true表示元素已经到达了目标值
        if (iBtn){
            clearInterval(oDom.iTimer);
        }

    },30);
}

function css(obj,attr) {
    if (obj.currentStyle) {
        return obj.currentStyle[attr];
    }else {
        return getComputedStyle(obj,false)[attr];
    }
}

运动回调,链式运动

我们在实际的运动当中,存在一种情况,就是我们需要当一个属性运动完成之后再去执行另外一个属性的变化。

想要实现这种功能,我们需要将我们的代码改成回调的形式。

首先,在我们的函数中,加入一个回调函数的参数

startMove(oDom,json,iSpeed,fn){}

至于调用的时机,我们可以放在一个属性动画完成之后再去执行回调,并且同时需要判断,是否存在回调,如果存在,在执行回调。

// 判断iBtn是否为true,为true表示元素已经到达了目标值
if (iBtn){
    clearInterval(oDom.iTimer);
    fn && fn.call(oDom); // 通过call改变this指向,方便我们后续的回调函数的调用。
}
        

下面我们来尝试一下,div点击之后,先将宽度变为200,宽度变化完成之后,再讲高度变为300.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <style>
            #div1 {
                width: 100px;
                height: 100px;
                background-color: red;
                position: absolute;
                top: 100px;
                left: 200px;
            }
        </style>
    </head>
    <body>
        <div id="div1"></div>
    </body>
    <script>
        let oDiv1 = document.getElementById('div1');

        oDiv1.onclick = function(){
            startMove(this,{
                width:200
            },10,function(){ // 传入一个匿名函数作为回调函数
                startMove(this,{height:300},10);
            });
        };

        function startMove(oDom,json,iSpeed,fn) { 

            clearInterval(oDom.iTimer);
            let iCur = 0; // 用这个变量来存储透明度处理的值
            oDom.iTimer = setInterval(()=>{

                // 设置一个变量,用来存储状态,表示元素是否到达目标值
                let iBtn = true;

                // 因为传入的参数是json,为了逐一操作,需要开启循环
                for(let attr in json) {
                    // 设置目标值
                    let iTarget = json[attr]; // 当前属性要运动的值就是目标值
                    // 进行操作判断
                    if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理
                        iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100
                    }else { // 其他的值可以直接设置
                        iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数
                    }

                    if (iCur !== iTarget) { // 判断是否等于目标的透明度
                        iBtn = false;
                        if(attr === 'opacity'){ // 对透明度的设置需要特殊处理
                            oDom.style.opacity = (iCur + iSpeed) / 100;
                            oDom.style.filter = 'alpha(opacity='+  (iCur + iSpeed) +')';
                        }else { // 其他属性值的设置
                            oDom.style[attr] = iCur + iSpeed + 'px';
                        }
                    }
                }   

                // 判断iBtn是否为true,为true表示元素已经到达了目标值
                if (iBtn){
                    clearInterval(oDom.iTimer);
                    fn && fn.call(oDom); // 改变this指向
                }

            },30);
        }

        function css(obj,attr) {
            if (obj.currentStyle) {
                return obj.currentStyle[attr];
            }else {
                return getComputedStyle(obj,false)[attr];
            }
        }

    </script>
</html>

上面的代码中 ,我们顺利的实现了代码的回调函数以及多属性延迟调用的功能。

缓冲运动

上面的代码中,我们已经基本实现了运动框架的基本构建。但是我们在实际使用的过程中,还存在着一种速度缓冲的现象,什么叫速度缓冲呢?简单的说
就是速度的逐渐变慢或者逐渐变快。

在本案例中将以速度逐渐变慢为例,来完成函数的再次升级。速度的逐渐变慢,也有人称之为摩擦运动。

首先,我们先来完成一个基本的demo。

我们先来在网页中放置一个按钮,一个div。当我们点击按钮的时候,让div发生运动。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <style>
            #div1 {
                width: 100px;
                height: 100px;
                background-color: red;
                position: absolute;
                top: 100px;
                left: 100px;
            }
        </style>
    </head>
    <body>
        <button id="btn">点击运动</button>
        <div id="div1"></div>
    </body>
    <script>
        // 获取按钮和元素
        let oBtn = document.getElementById('btn');
        let oDiv1 = document.getElementById("div1");
        // 给按钮绑定单击事件
        oBtn.onclick = function () {

        };

    </script>
</html>

上面的代码中,我们点击按钮之后,就会触发单击事件的事件处理函数。

下面来针对这个事件处理函数进行运动处理 。

// 获取按钮和元素
let oBtn = document.getElementById('btn');
let oDiv1 = document.getElementById("div1");
let iTimer = null;
let iSpeed = 50; // 定义一个初始速度
// 给按钮绑定单击事件
oBtn.onclick = function () {
/*摩擦运动:减速运动,在运动的过程中,运动速度越来越慢*/

    clearInterval(iTimer);

    iTimer = setInterval(function(){

        // 进行具体的速度判读
        if(oDiv1.offsetLeft === 500 ){
            clearInterval(iTimer);
        }else {
            oDiv1.style.left = oDiv1.offsetLeft + iSpeed + 'px';
        }

    },30);

};

上面的代码完毕之后,我们点击按钮之后元素以匀速的形式到达了目标点。

下面我们来处理一下如何让我们的元素以摩擦减速的效果到达目标点。

此时我们应该知道,我们希望得到的减速运动效果,特征是越接近于目标点,速度应该越慢。


let oBtn = document.getElementById('btn');
let oDiv1 = document.getElementById("div1");
let iTimer = null;

oBtn.onclick = function () {
/*摩擦运动:减速运动,在运动的过程中,运动速度越来越慢*/

    clearInterval(iTimer);
    let iSpeed = 0;
    iTimer = setInterval(function(){

        // 处理速度
        iSpeed = (500 - oDiv.offsetLeft) / 8;
        // 当元素的位置超过了目标位置,那么元素运动就始终到达不了目标位置,所以需要下面这种形式的判断。
        iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed); //速度如果大于0,向上取整,如果小于0,向下取整  

        // 进行具体的速度判读
        if(oDiv1.offsetLeft === 500 ){
            clearInterval(iTimer);
        }else {
            oDiv1.style.left = oDiv1.offsetLeft + iSpeed + 'px';
        }

    },30);

};

在上面的代码中,为了实现我们缓冲运动的需求,我们通过距离越短,速度越慢的原理实现了渐变的速度,同时,为了防止速度最终没有
办法达到目标值,所以使用了取整的方式。

下面是案例的完整代码:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <style>
            #div1 {
                width: 100px;
                height: 100px;
                background-color: red;
                position: absolute;
                top: 100px;
                left: 100px;
            }
        </style>
    </head>
    <body>
        <button id="btn">点击运动</button>
        <div id="div1"></div>
    </body>
    <script>
        let oBtn = document.getElementById('btn');
        let oDiv1 = document.getElementById("div1");
        let iTimer = null;

        oBtn.onclick = function () {
            /*摩擦运动:减速运动,在运动的过程中,运动速度越来越慢*/

            clearInterval(iTimer);
            let iSpeed = 0;
            iTimer = setInterval(function(){

                // 处理速度
                iSpeed = (500 - oDiv1.offsetLeft) / 8;
                iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed); //速度如果大于0,向上取整,如果小于0,向下取整

                // 进行具体的速度判读
                if(oDiv1.offsetLeft === 500 ){
                    clearInterval(iTimer);
                }else {
                    oDiv1.style.left = oDiv1.offsetLeft + iSpeed + 'px';
                }

            },30);

        };

    </script>
</html>

上面我们弄清楚了缓冲的原理之后,再来将缓冲加到我们之前封装的运动框架当中。

加入缓冲的运动框架

我们上面了解了缓冲运动的原理,下面来进行一个简单的总结。

缓冲运动

  • 缓冲运动与摩擦力的区别: 能够精确的到达目标位置。
  • 同时,速度由距离决定。
  • 速度 = (目标值 - 当前值 ) / 缩放系数
  • 值需要取整
function startMove(oDom, json, fn) {
        clearInterval(oDom.iTimer);
        var iCur = 0;
        var iSpeed = 0;

        oDom.iTimer = setInterval(function() {

            var iBtn = true;

            for ( var attr in json ) {

                var iTarget = json[attr];

                if (attr === 'opacity') {
                    iCur = Math.round(css( oDom, 'opacity' ) * 100);
                } else {
                    iCur = parseInt(css(oDom, attr));
                }

                iSpeed = ( iTarget - iCur ) / 8;
                iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);

                if (iCur !== iTarget) {
                    iBtn = false;
                    if (attr === 'opacity') {
                        oDom.style.opacity = (iCur + iSpeed) / 100;
                        oDom.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')';
                    } else {
                        oDom.style[attr] = iCur + iSpeed + 'px';
                    }
                }

            }

            if (iBtn) {
                clearInterval(oDom.iTimer);
                fn && fn.call(oDom);
            }

        }, 30);
    }

    function css(oDom, attr) {
        if (oDom.currentStyle) {
            return oDom.currentStyle[attr];
        } else {
            return getComputedStyle(oDom, false)[attr];
        }
    }

我们来通过我们的运动框架来让元素发生一个缓冲运动。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>运动框架下的缓冲运动</title>
    <style>
        #div1 {
            width: 100px;
            height: 100px;
            background-color: red;
            position: absolute;
            top: 100px;
            left: 100px;
        }
    </style>
</head>
<body>
<button id="btn">点击运动</button>
<div id="div1"></div>
</body>
<script>
    let oBtn = document.getElementById('btn');
    let oDiv1 = document.getElementById('div1');

    oBtn.onclick = function () {
        // startMove(oDiv1,{
        //     width:800,
        //     height:800
        // });
        // 也可以尝试回调多属性操作
        startMove(oDiv1,{
            width:200
        },function () {
            startMove(oDiv1,{height:200})
        })
    };

    function startMove(oDom, json, fn) {
        clearInterval(oDom.iTimer);
        var iCur = 0;
        var iSpeed = 0;

        oDom.iTimer = setInterval(function () {

            var iBtn = true;

            for (var attr in json) {

                var iTarget = json[attr];

                if (attr === 'opacity') {
                    iCur = Math.round(css(oDom, 'opacity') * 100);
                } else {
                    iCur = parseInt(css(oDom, attr));
                }

                iSpeed = (iTarget - iCur) / 8;
                iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);

                if (iCur !== iTarget) {
                    iBtn = false;
                    if (attr === 'opacity') {
                        oDom.style.opacity = (iCur + iSpeed) / 100;
                        oDom.style.filter = 'alpha(opacity=' + (iCur + iSpeed) + ')';
                    } else {
                        oDom.style[attr] = iCur + iSpeed + 'px';
                    }
                }

            }

            if (iBtn) {
                clearInterval(oDom.iTimer);
                fn && fn.call(oDom);
            }

        }, 30);
    }

    function css(oDom, attr) {
        if (oDom.currentStyle) {
            return oDom.currentStyle[attr];
        } else {
            return getComputedStyle(oDom, false)[attr];
        }
    }
</script>
</html>

原文地址:https://www.cnblogs.com/liujunhang/p/10888581.html

时间: 2024-10-10 09:51:06

JavaScript 基础入门11 - 运动框架的封装的相关文章

JavaScript基础入门07

目录 JavaScript 基础入门06 BOM window对象 Navigator对象 Screen 对象 Location对象 History 对象 JavaScript 基础入门06 BOM 当js的运行环境为浏览器时,学习js应该将其分成三个部分:ECMAScript核心语法.BOM.DOM. BOM我们可以将其称之为浏览器对象模型,主要描述了与浏览器进行交互的方法和接口. IE 3.0 和 Netscape Navigator 3.0 提供了一种特性 - BOM(浏览器对象模型),可

javascript基础入门之js中的数据类型与数据转换01

javascript基础入门之js中的数据结构与数据转换01 js的组成(ECMAScript.BOM.DOM)        js中的打印语句:        数据类型        变量        运算符        数据类型转换        js中三大特殊值 js的组成(ECMAScript.BOM.DOM) ①ECMAScript: ECMAScript是一个标准,它规定了语法.类型.语句.关键字.保留子.操作符.对象.(相当于法律):②BOM(浏览器对象模型):对浏览器窗口进行

【JavaScript基础入门】总结目录

一.JavaScript基础 1.1JavaScript概述 1.2如何使用的JavaScript 1.3JavaScript基本语法 1.4JavaScript数据类型 1.5JavaScript运算符 1.6JavaScript变量作用域和生命周期 1.7输出语句 二.JavaScript流程控制 三.JavaScript字符串和数组 3.1字符串 3.2数组 四.对象和函数 4.1对象 4.2对象的原型 4.3函数 4.4函数参数 五.JavaScript错误处理 六.JavaScript

JavaScript基础入门 - 01

JavaScript入门 - 01 准备工作 在正式的学习JavaScript之前,我们先来学习一些小工具,帮助我们更好的学习和理解后面的内容. js代码位置 首先是如何编写JavaScript代码,说到这,我们首先要来说明一个需要新人们关注的点,因为我们的js是一门跨平台的语言,所以说,我们的代码可以运行在不同的平台之上.这也就导致了可能相同的代码放在不同的平台运行就会有所出入. 这里面说的平台其实被称之为宿主环境. 同时,代码在不同的平台上的运行方式也有所不同. 如果运行在服务端,那么更多的

JavaScript基础入门教程(二)

说明 前一篇博客介绍了js以及一些关于js基本类型的简单知识,本篇博客将详细介绍js的基础类型,捎带介绍对象类型,更详细的对象类型的说明将后续再讲. js中类型的说明 js中的类型分为基本类型和对象类型,其中基本类型包括:①数字.②字符串.③布尔值.此外还有两个原始值null和undefined.其中对象包括数组对象.函数对象和普通对象,普通对象是"命名值"的无序集合,而数组对象是带编号的值的有序集合.JavaScript核心还定义了三种有用的类:日期(Data)类.正则(RegExp

JavaScript基础入门教程(四)

说明 前面三篇博客介绍了js中基本的知识点,包括变量类型及其转换.表达式.运算符等小知识点,这篇博客主要讲的是对象.如果你学过java等语言,你也许在下文的阅读中发现在js中的对象与java中的对象存在一定的区别. 相关术语说明 一.对象中成员变量的"属性特性" ①可写:表明该属性可以设置其值. ②可枚举:表明是否可以通过for/in循环返回该属性. ③可配置:表明是否可以删除或者修改该属性. 注:关于for/in等语句的说明,第一篇博文就说过,本系列教程是建立在读者对基本的编程有一定

JavaScript基础入门01

JavaScript能用来做什么? 页面分为:结构.样式.行为. JavaScript的组成: ECMAScript.BOM.DOM ECMAScript是一个标准,它规定了语法.类型.语句.关键字.保留子.操作符.对象.(相当于法律) BOM(浏览器对象模型):可以访问浏览器窗口的浏览器对象模型,对浏览器窗口进行操作. DOM(文档对象类型):DOM把整个页面映射成一个多层节点结构.HTML页面组成都是某种类型的节点,这些节点又包含着不同类型的数据 js代码位置 首先是如何编写JavaScri

JavaScript基础入门知识

JavaScript三种使用方式 JavaScript代码屏蔽 JavaScript内容显示的位置 JavaScript中的错误及解决方法 1.语法错误:通过控制台可以检查并解决. 2.逻辑错误:通过alert()插入来进行判断并解决. JavaScript中的小案例 1.输出1~100之间奇数或者偶数的和: 2.输出一个九九乘法表: 3.百钱买百鸡: JavaScript中特别的判断方法 JavaScript中通过arguments对象得到每一个参数的值并相加 JavaScript中通过arg

《慕客网:IOS基础入门之Foundation框架初体验》学习笔记 &lt;五&gt; NSDicionary

1 int main(int argc, const char * argv[]) { 2 @autoreleasepool { 3 //字典, 存储的内存不是连续的 用key和value进行对应(键值) 4 //kvc 键值编码 5 NSDictionary *dic = [NSDictionary dictionaryWithObject:@"1" forKey:@"a"]; 6 NSLog(@"%@",dic);//以上的方法是不常用的 7