使用源生JS自定义动画(支持多个属性)

教学笔记 智学无忧-老曹

通过自定义动画,可以很好提升对源生JavaScript知识的理解,主要涉及的技术:

1、setInterval 最小支持10毫秒,低于10毫秒按10秒计算,对于Chrome最少可以支持到4ms。

2、setInterval处理函数作用域,使用let锁定处理函数变量的作用

3、动画原理,在明确动画持续时间下,划分多个时间片段,改变元素的状态,达到目标值为止,结束动画

4、需要有面向对象思维,处理多个属性的动画,使用{}构建多个动画的信息


完整代码

/**
 * 自定义元素的动画,支持多个属性
 * 
 * @param {Object} el 动画元素
 * @param {Object}} option 动画参数 {left:50,top:500}   注意:不要带单位  错误{left:50px} 正确{left:50}
 * @param {Number} speed 动画持续时间(秒)
 */
function animate(el, option, speed = 0.5) {
    if (!el || !option) {
        return;
    }
    function run() {
        animateData = {};//动画数据
        var needTime = speed * 1000;
        var cs = window.getComputedStyle(el);
        //计算方向、前进距离 
        //这里使用let的原因是锁定key的作用域,否则setInterval执行函数的key值总是最后一次循环的key,就不能实现多个属性的动画效果
        for (let key in option) {
            var currValue;//当前值
            animateData[key] = {};
            //样式属性还是普通属性
            animateData[key].whereProperty = null;
            if ((currValue = cs[key])) {
                animateData[key].whereProperty = "style";//自定义属性
            } else if ((currValue = el[key])) {
                animateData[key].whereProperty = "attr";//普通属性
            }
            if (!currValue) {
                continue;
            }
            //目标值 
            var targetValue = parseFloat(option[key]);
            //是否带px
            animateData[key].isPx = false;//是否带px
            if ((currValue + "").endsWith("px")) {//带px
                animateData[key].isPx = true;
            }
            //方向
            animateData[key].direction = "-";
            if (targetValue > parseFloat(currValue)) {
                animateData[key].direction = "+";
            }
            //当前值(不带单位)
            animateData[key].value = parseFloat(currValue);
            //每次移动的距离(需要计算)
            animateData[key].step = (targetValue - animateData[key].value) / (needTime / 10);
            //开始移动
            var count = 0;
            animateData[key].interval = setInterval(function () {
                //需通过key重新获取,不能直接使用targetValue,否则在serInterval中使用的是最后的targetValue
                var targetValue = parseFloat(option[key]);
                animateData[key].value += animateData[key].step;//正方向或负方向
                //达到目标
                if ((animateData[key].direction == "+" && animateData[key].value >= targetValue)
                    || (animateData[key].direction == "-" && animateData[key].value <= targetValue)
                ) {
                    animateData[key].value = targetValue;//防止超过目标
                }
                // start 移动操作
                if (animateData[key].whereProperty == "style") {
                    el.style[key] = animateData[key].value + (animateData[key].isPx ? "px" : 0);
                } else {
                    el[key] = animateData[key].value + (animateData[key].isPx ? "px" : 0);
                }
                // end 移动操作

                //移动结束,停止计时器
                if (animateData[key].value == targetValue) {
                    clearInterval(animateData[key].interval);
                    return;
                }
            }, 10);
        }
    }
    run();
}

还能输出{{restrictNumber}}个字符  
  • {{reply.author}}

    {{CommonUtil.formateDate(reply.ac_CommentDate).shortTime}}
  • 回复了{{Comments.author}} :