运动函数

115 阅读6分钟
    .box {
        width: 200px;
        height: 200px;
        background-color: aqua;
        position: absolute;
        top: 0;
        left: 0;
        }
     * {
        margin: 0;
        padding: 0;
       }
      <div class="box"></div>

需求1 :

  • 点击 box 后让他的 left 值 更改为 500
        var box = document.querySelector('.box')
        // 1. 给元素绑定点击事件
        box.onclick = function(){
            this.style.left = 500 +'px'
        }
  • 新需求 : 让移动有一个动画效果

需求2 :

  • 点击 box 后 让他的 top 值 更改为 500
        var box = document.querySelector('.box')
        // 1. 给元素绑定事件
        box.onclick = function () {
            // 调用运动函数
            move(this, 'top', 300)
        }
        // 2. 封装运动函数, 方便多次调用, 减少代码量
        function move(ele, type, target) {
            /**
             *  参数解析(形参名无所谓):
             *      ele: 移动的目标元素
             *      type: 修改目标的那个属性
             *      target: 移动到那个位置
            */
            // 1. 创建初始值
            let init = 0
            // 2. 开启定时器, 每 20 ms 执行一次
            const timer = setInterval(() => {
                // 2.1 修改初始值
                init += 5
                // 2.2 将修改后的初始值赋值给元素的对应属性
                ele.style[type] = init + 'px'
                // 2.3 判断移动到指定位置后, 结束定时器
                if (init >= target) clearInterval(timer)
            }, 20)
        }

需求3 :

  • 原本坐标 top 为 200 移动到 400
    • 问题 : 不管元素的原来坐标值是什么, 每次运动都是从0 开始
    • 解决 : 修改 init 的初始值为 元素本身的属性值
       var box = document.querySelector('.box')
        box.onclick = function () {
            move(this, 'top', 300)
        }
        function move(ele, type, target) {
            // 获取原本的值, 在style里, 如何获取?
            // window.getComputedStyle(ele)
            // parseInt : 进行取整 为什么?
            // 原来是100px,是一个字符串,自身每次 +5 '100px' + 5 = '100px5'
            let init = parseInt(window.getComputedStyle(ele)[type])
            const timer = setInterval(() => {
                init += 5
                // type 为什么要加 [] ? 
                // type 是一个变量, 里面可以是任何属性, 比如 top/left/width等
                ele.style[type] = init + 'px'
                if (init >= target) clearInterval(timer)
            }, 50)
        }

需求4 :

  • 原来坐标 left 为 200 移动到 501
  • 原来坐标 top 为 200 移动到 602
  • 问题 : 移动的距离不是 5的倍数, 不能准确的移动到目标值上

  • 解决 :

1. 每次移动的 距离为 5, 不能准确的移动到 目标值
    • 速度固定, 如果距离不同, 那么需要的时间也不同
2. 每次移动的 总路程为 1/10
    • 假设 当前位置 为 200, 要 移动到 501, 移动的 总距离为 301, 那么每次移动 30.1
3. 每次移动 剩余路程的 1/10
    • 假设当前位置 为 200,要 移动到 501, 移动的 总距离为 301
    • 第一次 移动的距离为 30.1
    • 第二次 移动的时候, 当前位置为 230.1 ( 200 + 30.1), 要移动到501, 移动的总距离为 501 - 230.1
        var box = document.querySelector('.box')

        box.onclick = function () {
            move(this, 'left', 100)
        }

        function move(ele, type, target) {
            // 1. 开启定时器
            const timer = setInterval(() => {
                // 2. 获取当前位置
                let init = parseInt(window.getComputedStyle(ele)[type])
                // 3. 计算本次移动距离  (目标 - 当前值) / 10
                let duration = (target - init) / 10

                // 4. 判断走还是不走
                if (target <= init) {
                    clearInterval(timer)
                } else {
                    // 元素原本的位置 + 要移动的距离, 然后赋值给元素
                    ele.style[type] = init + duration + 'px'
                }
            }, 20)
        }
  • 问题 : 从 0px 移动, 移动到 100px, 但实际移动到 91.9px 就停止了

  • 分析 :
    • 在某一次移动到 91px ,当前位置为 91px, 但要移动到 100px
    • 我们的方式为 移动剩余路程的 1/10
    • 也就是 ( 目标值 - 当前值 ) / 10 -> ( 100 - 91 ) / 10 === 0.9
    • 所以本次移动的距离为 0.9px, 也就是说从 91px 移动到了 91.9px
    • 因为浏览器的最小像素表述单位为 1px, 所以虽然设置了 0.9, 但浏览器实际无法移动到 0.9px
    • 所以我们下一次获取到这个元素位置的时候, 还是在 91px
  • 解决 :
    • 让每次移动的最小值 向上取整为 1px ( Math.ceil() )
  • 新问题 : 从 100px 移动到 0px, 但实际上移动到 9px 就停止了
    1. 每次移动的距离 ( 目标值 - 当前值 ) / 10 -> ( 0 - 9 ) / 10 === -0.9
    1. 计算完成这个移动距离后, 判断是否小于1,如果小于1,那么向下取整为 -1 ( Math.floor() )
    • 如果 目标值 大于 当前值 === ( 目标值 向右/向下 ) , 向上取整
      • 比如 : 0.9px 向上取整为 1px
    • 如果 目标值 小于 当前值 === ( 目标值 向左/向上 ) , 向下取整
      • 比如 : -0.9px 向上取整为 -1px
        var box = document.querySelector('.box')

        box.onclick = function () {
            stop(this, 'left', 501)
            console.log('点击了');
        }

        function stop(ele, type, target) {
            // 1. 开启定时器
            const timer = setInterval(() => {
                // 2. 获取当前位置
                let init = parseInt(window.getComputedStyle(ele)[type])
                // 3. 计算本次移动距离 (每次移动1 /10)  (目标 - 当前值) / 10 
                let duration = (target - init) / 10
                duration = duration > 0 ? Math.ceil(duration) : Math.floor(duration)

                // 4. 判断移动距离 如果小于1 那么向上取整
                // if (duration < 1) {
                //     duration.Math.ceil(deration)
                // } // 第一种写法
                // duration < 1 ?  duration.Math.ceil(deration) : '' // 第二种写法
                // 4. 判断走还是不走
                if (target === init) {
                    clearInterval(timer)
                } else {
                    // 元素原本的位置 + 要移动的距离 然后赋值给元素
                    ele.style[type] = init + duration + 'px'
                }
            }, 20)
        }

运动函数完结篇

  • 问题 : 改变多个属性时, 需要调用多次 move 函数

  • 解决 :
1. 多写参数 , 但是并不友好
        box.onclick = function () {
             move(this, 'left', 100)
             move(this, 'top', 100)
             move(this, 'width', 400)
        }
2. 将参数修改为 对象格式, 修改哪些属性值, 就在对象内部修改
        box.onclick = function () {
            move(this, {
                left: 100,
                top: 100,
                width: 300
            })
        }

        function move(ele,options){
            // 取出对象属性 for in
            for(let key in options){
                // console.log(key,options[key]);
                let type = key  // key 就是 type
                let target = options[key] // value 就是 目标值
                let timer = setInterval( () => {
                    // 获取当前位置
                    let init = parseInt(window.getComputedStyle(ele)[type]) 
                    // 计算本次距离
                    let duration = (target - init) / 10  // (500 - 100)/10 = 40
                    // 判断移动的距离
                    // 如果距离 > 0 那么向上取整 如果距离 < 0 那么向下取整
                    duration = duration > 0 ? Math.ceil(duration) : Math.floor(duration)
                    // 判断是否 到达 目标值
                    if(init === target){
                        clearInterval(timer)
                    }else{
                        ele.style[type] = init + duration + 'px'
                    }

                },20)
            }
        }

运动函数大结局

  • 问题 : 没有办法真正捕获到运动那个函数的结束

  • 解决 :
    • 利用计数器思想
    1. 在函数开始时创建一个变量 作为计数器(变量名与变量的值不重要)
    1. for...in 循环每执行一次, 就代表开启了一个运动函数, 那么将 count 的值自增 1
    1. clearInterval 每执行一次, 说明有一个运动函数结束了, 所以此时将 count 值 自减1
    1. 当 count 的值再一次 等于 0 时( 因为初始值给的0 ), 说明所有的运动函数执行完毕,此时为 move 函数真正的结束
        var box = document.querySelector('.box')
        box.onclick = function () {
            console.log('点击了');
            move(this, {
                left: 100,
                top: 100,
                width: 300
            })
        }
        function move(ele, options) {
            // 创建一个变量
            let count = 0;
            for (let key in options) {
                let type = key
                let target = options[key]

                // 循环每执行一次, 让计时器自增一次
                count++

                // 1. 开启计时器
                const timer = setInterval(() => {
                    // 2. 获取当前值
                    let init = parseInt(window.getComputedStyle(ele)[type])
                    // 3. 计算本次距离
                    let duration = (target - init) / 10
                    duration = duration > 0 ? Math.ceil(duration) : Math.floor(duration)
                    // 判断是否达到目标值
                    if (init === target) {
                        clearInterval(timer)
                        // 每清除一个计时器, 说明有一个运动函数结束了,将count --
                        count --
                        if(count === 0){
                            // 当分支执行,说明所有运动函数执行完毕了
                            console.log('执行完毕');
                        }
                    } else {
                        ele.style[type] = init + duration + 'px'
                    }

                }, 20)
            }
        }

运动函数的彩蛋

回调函数
  • 本质上就是一个函数
    1. 把 函数A, 以实参的形式传递到 函数B 内部
    1. 在 函数B 内部, 以 形参的方式 调用 函数A
    1. 此时 我们可以把 函数A 叫做 函数B 的回调函数
        box.onclick = function () {
            console.log('点击了');
            move(this, {
                width: 200,
                height: 300,
                top: 200,
                left: 300
            }, () => {
                // console.log('我是一个回调函数');
                box.style.backgroundColor = 'red'
            })
        }
        // 2. 封装运动函数
        function move(ele, options, fn) {
            // 创建一个计时器
            let count = 0
            // 遍历对象属性 for in
            for (let key in options) {
                let type = key
                let target = options[key]
                // 循环执行一次,计数器自增一次
                count++
                const timer = setInterval(() => {
                    // 获取当前值
                    let init = parseInt(window.getComputedStyle(ele)[type])
                    // 3. 计算本次距离
                    let duration = (target - init) / 10
                    duration = duration > 0 ? Math.ceil(duration) : Math.floor(duration)
                    // 判断是否达到目标值
                    if (init === target) {
                        clearInterval(timer)
                        // 清除一个计时器, 说明一个运动结束
                        count--
                        if (count === 0) {
                            // console.log('执行完毕');
                            fn()
                        }

                    } else {
                        ele.style[type] = init + duration + 'px'
                    }
                }, 20)
            }
        }