Javascript原生代码——动画封装:匀速、缓速及升级

      在web网页设计中,经常要使用到动画(如轮播图、随滚动窗口移动的广告栏等等),封装动画会节省很多代码,也让结构更简单清晰。这里先封装了一个匀速移动的动画和一个缓速移动的动画(通过offsetLeft/offsetTop来左右上下移动),然后逐渐深入下去。

匀速动画

ele为要移动的盒子,target为目标位置(像素),spd为计数器的频率

function animate1(ele,target,spd){
        //要用定时器,先清除定时器,一个盒子只能有一个定时器,这样的话,不会和其他盒子出现定时器冲突
        //而定时器本身将成为盒子的一个属性

        clearInterval(ele.timer);
        //我们要求盒子既能向前又能向后,那么我们的步长就得有正有负
        //目标值如果大于当前值取正,目标值如果小于当前值取负

        var speed = target>ele.offsetLeft?10:-10;
        ele.timer = setInterval(function () {

            //在执行之前就获取当前值和目标值之差
            var val = target - ele.offsetLeft;
            ele.style.left = ele.offsetLeft + speed + "px";

            //目标值和当前值只差如果小于步长,那么就不能前进了
            //因为步长有正有负,所有转换成绝对值来比较

            if(Math.abs(val)<Math.abs(speed)){
                ele.style.left = target + "px";
                clearInterval(ele.timer);
            }
        },spd)

    }

 

缓速动画(先快后慢)

ele为要移动的盒子,target为目标位置(像素),spd为计数器的频率

function animate2(ele,target,spd) {
        //要用定时器,先清定时器
        clearInterval(ele.timer);
        //定义定时器
        ele.timer = setInterval(function () {
            //获取步长
            //步长应该是越来越小的,缓动的算法。

            var step = (target-ele.offsetLeft)/10;
            //对步长进行二次加工(大于0向上取整,小于0向下取整)
            step = step>0?Math.ceil(step):Math.floor(step);
            //动画原理: 目标位置 = 当前位置 + 步长
            ele.style.left = ele.offsetLeft + step + "px";
            //检测缓动动画有没有停止
            console.log(“完成”);
            if(Math.abs(target-ele.offsetLeft)<=Math.abs(step)){
                //处理小数赋值
                ele.style.left = target + "px";
                clearInterval(ele.timer);
            }
        },spd);

 

    }

 

以上动画通过控制offsetLeft来移动,只能在父盒子中左右移动,我们如果想要盒子在窗口任意位置移动呢?

(前面的封装方法通过offsetLeft/offsetTop来左右上下移动,其实用元素距离父盒子的距离代替元素本身的top、left属性。为什么不直接用left、top呢?

因为div.style.left此类方法是完全对行内式来操作的,对它赋值没有问题,但是在获取值的时候如果本身没有定义则获取不到。导致我们获取left值时,是用offsetLeft模拟)

学习了一个新的方法,可以获取任意类型的CSS样式的属性值,它的兼容性写法如下

 

function getStyle(ele,attr){
    if(window.getComputedStyle){
        return window.getComputedStyle(ele,null)[attr];
    }
    return ele.currentStyle[attr];
}

基于此,我们将之前的缓速动画进行了新的封装

缓速动画(先快后慢)(单个属性)

 

这里ele表示移动的元素,attr表示要改变的属性,target表示要修改的属性值

function animate(ele,attr,target){
    //先清定时器
    clearInterval(ele.timer);
    ele.timer = setInterval(function () {
        var leader = parseInt(getStyle(ele,attr)) || 0;
        //1.获取步长
        var step = (target - leader)/10;
        //2.二次加工步长
        step = step>0?Math.ceil(step):Math.floor(step);
        leader = leader + step;
        //3.赋值
        ele.style[attr] = leader + "px";
         //4.清除定时器
        if(Math.abs(target-leader)<=Math.abs(step)){
            ele.style[attr] = target + "px";
            clearInterval(ele.timer);
        }
    },25);
}

 

上面的动画,一次调用只能更改一个属性,如果我们要同时更改多个属性要怎么做?

封装一个可以对多个属性同时改变的缓动动画

缓速动画(先快后慢)(多个属性)

这里ele表示要移动的盒子,Json存储了它要改变的多个属性:值。

function animate(ele,json){
    //先清定时器
    clearInterval(ele.timer);
    ele.timer = setInterval(function () {
        //开闭原则
        var bool = true;
        //遍历属性和值,分别单独处理json
        //attr == k(键)    target == json[k](值)
        for(var k in json){
            //四步
            var leader = parseInt(getStyle(ele,k)) || 0;
            //1.获取步长
            var step = (json[k] - leader)/10;
            //2.二次加工步长
            step = step>0?Math.ceil(step):Math.floor(step);
            leader = leader + step;
            //3.赋值
            ele.style[k] = leader + "px";
            //4.清除定时器
            //判断: 目标值和当前值的差大于步长,就不能跳出循环
            //不考虑小数的情况:目标位置和当前位置不相等,就不能清除清除定时器。
            if(json[k] !== leader){
                bool = false;
            }
        }
        console.log(1);
        //只有所有的属性都到了指定位置,bool值才不会变成false;
        if(bool){
            clearInterval(ele.timer);
        }
    },25);
}

注意到这里清除定时器的方法用到了开闭原则。另外,将需要更改的多个属性值,存在json中,举个例子

 var json = {"left":10,"top":200,"width":300,"height":200};

这个动画还可以再升级,如果我们将盒子挪到一个新地方,同时改变了各种属性,完成了这一系列动作后,我们又要让他返回原来的属性或者再变化成一个新的属性,而又不想重复调用,触发两次事件,要怎么做?

套用回调函数

缓速动画(先快后慢)(多个属性+回调函数)

这里ele表示要移动的盒子,Json存储了它要改变的多个属性:值。fn表示新的函数

 

function animate(ele,json,fn){
    //先清定时器
    clearInterval(ele.timer);
    ele.timer = setInterval(function () {
        //开闭原则
        var bool = true;
        //遍历属性和值,分别单独处理json
        //attr == k(键)    target == json[k](值)
        for(var k in json){
            //四部
            var leader = parseInt(getStyle(ele,k)) || 0;
            //1.获取步长
            var step = (json[k] - leader)/10;
            //2.二次加工步长
            step = step>0?Math.ceil(step):Math.floor(step);
            leader = leader + step;
            //3.赋值
            ele.style[k] = leader + "px";
            //4.清除定时器
            //判断: 目标值和当前值的差大于步长,就不能跳出循环
            //不考虑小数的情况:目标位置和当前位置不相等,就不能清除清除定时器。
            if(json[k] !== leader){
                bool = false;
            }
        }
        console.log(1);
        //只有所有的属性都到了指定位置,bool值才不会变成false;
        if(bool){
            clearInterval(ele.timer);
            //所有程序执行完毕了,现在可以执行回调函数了
            //如果存在fn,执行fn
            if(fn){
                fn();
            }
        }
    },25);
}

在所有第一步程序执行完毕后,判断有没有回调函数,如果有,执行它。

调用方式:

animate(div,json2, function () {
                    animate(div,json1);

                });

这是可以层层往里套用的。

做到这里,发现还有一个问题,对于没有px单位的属性值,上面的封装框架无法赋值,如 

border-radius:1px 2px 4px 6px;  //分别赋值:div.style.borderTopLeftRadius="1px";

opacity:0.5;//见下文

background:rgba(0,0.5,0,0.4);//Jquary中调用插件来赋值,暂且不谈

z-index:2;//见下文

如果在我们前面的Json中加入opacity和Z-index,则需要再加入句子,特殊取值特殊处理

(基本思路,对于opacity,要写成百分制,在取值时对当前值*100,赋值时再/100;对于z-index,不用缓动赋值,直接一步到位)

加入了透明度和层级,对程序进行优化

var json = {"left":300,"top":200,"width":300,"height":200,"opacity": 30,"zIndex": 5};

 

缓速动画(先快后慢)(多个属性+回调函数+兼容透明度和层级属性)

 

function animate(ele,json,fn){
    //先清定时器
    clearInterval(ele.timer);
    ele.timer = setInterval(function () {
        //开闭原则
        var bool = true;
        //遍历属性和值,分别单独处理json
        //attr == k(键)    target == json[k](值)
        for(var k in json){
            var leader;
            //判断如果属性为opacity的时候特殊获取值
            if(k === "opacity"){
                leader = getStyle(ele,k)*100 || 1;
            }else{
                leader = parseInt(getStyle(ele,k)) || 0;
            }
            //1.获取步长
            var step = (json[k] - leader)/10;
            //2.二次加工步长
            step = step>0?Math.ceil(step):Math.floor(step);
            leader = leader + step;
            //3.赋值
            //特殊情况特殊赋值
            if(k === "opacity"){
                ele.style[k] = leader/100;
                //兼容IE678
                ele.style.filter = "alpha(opacity="+leader+")";
            //如果是层级,一次赋值成功,不需要缓动赋值
            }else if(k === "zIndex"){
                ele.style.zIndex = json[k];
            }else{
                ele.style[k] = leader + "px";
            }
            //4.清除定时器
            //判断: 目标值和当前值的差大于步长,就不能跳出循环
            //不考虑小数的情况:目标位置和当前位置不相等,就不能清除清除定时器。
            // if(json[k] !== leader){
            if(Math.abs(json[k]-leader)>Math.abs(step)){
                bool = false;
            }
        }
        console.log(1);
        //只有所有的属性都到了指定位置,bool值才不会变成false;
        if(bool){
            clearInterval(ele.timer);
            //所有程序执行完毕了,现在可以执行回调函数了
            //只有传递了回调函数,才能执行
            if(fn){
                fn();
            }
        }
    },25);
}

     目前就知道这些,但是还是有一个bug没有解决。在IE中可以清除定时器,但是在谷歌浏览器中无法清除定时器,从而无法进入函数执行回调函数。这个问题尚有待解决

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值