为了账号安全,请及时绑定邮箱和手机立即绑定

setTimeout()实现动画—常犯的错误

标签:
JavaScript

这是在学习(nodejs基础2中第1-2节)自己犯的一个错误,记下来防止后面再次发生吧。先把错误代码放上:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>moveball</title>
    <style>
        .ball
        {
            width: 100px;
            height: 100px;
            border-radius: 50px;
            margin-top: 50px;
        }
        .ball1
        {
            background-color: pink;
        }
        .ball2
        {
            background-color: orange;
        }
        .ball3
        {
            background-color: seagreen;
        }
    </style>
</head>
<body>
    <div class="ball ball1" style="margin-left: 0;"></div>
    <div class="ball ball2" style="margin-left: 0;"></div>
    <div class="ball ball3" style="margin-left: 0;"></div>

    <script>
        var ball1 = document.querySelector('.ball1');
        var ball2 = document.querySelector('.ball2');
        var ball3 = document.querySelector('.ball3');
        function animate(ball,target,callback)
        {
            setTimeout(function(){
                var marginLeft = parseInt( ball.style.marginLeft,10 );
                if( marginLeft === target )
                {
                    callback && callback();
                }else if( marginLeft < target ){
                        marginLeft++;
                    }else{
                        marginLeft--;
                    }
                } 
                ball.style.marginLeft = marginLeft + 'px';
                animate(ball,target,callback);
            },15);
        }
        animate(ball1,100,function(){
            animate(ball2,200,function(){
                animate(ball3,300,function(){ //运行到这里就会停止
                    animate(ball3,150,function(){
                        animate(ball2,150,function(){
                            animate(ball1,150,function(){});
                        });
                    });
                }); 
            });
        });

    </script>
</body>
</html>

运行上面这个代码后,会发现小球就不会回到150这个点的,但奇怪的是如果按照1-2-3-1-2-3这样的运动顺序:

animate(ball1,100,function(){
            animate(ball2,200,function(){
                animate(ball3,300,function(){
                    animate(ball1,150,function(){
                        animate(ball2,150,function(){
                            animate(ball3,150,function(){});
                        });
                    });
                }); 
            });
        });

小球就都会移动到150这个点,但能明显感觉到这个过程并不是匀速,而是越来越快,那么第一感想到的就是需要清理定时器(因为这情况简直跟setInterval()动画出现的问题一样):

function animate(ball,target,callback)
        {
            this.time = setTimeout(function(){
                var marginLeft = parseInt( ball.style.marginLeft,10 );
                if( marginLeft === target )
                {
                    clearTimeout( this.time );                  
                    callback && callback();
                }else if( marginLeft < target ){
                        marginLeft++;
                    }else{
                        marginLeft--;
                    }
                } 
                ball.style.marginLeft = marginLeft + 'px';
                animate(ball,target,callback);
            },15);
        }

运行上面这个代码后,会发现小球终于匀速地滚到了目标位置,但这并没有解决一个问题,那就是小球如果一旦按照1-2-3-3-2-1的顺序执行,依然会卡在1-2-3这个地方后面的3-2-1就根本不执行。很是折腾,打开调式面板会发现运行正常,后台有‘test’打印出来,但就是不执行callback():

if( marginLeft === target )
 {           
       console.log('test');
       callback && callback();
 }

在细看html标签中magin-left值会保持300的跳动,才明白原来这里的setTimeout并没有停止,所以就没办法进入自身的下一次定时器。
那么为什么没有停止呢?回来再次阅读if()代码块找到了一个逻辑错误之处:

this.time = setTimeout(function(){
                var marginLeft = parseInt( ball.style.marginLeft,10 );
                if( marginLeft === target )
                {
                    clearTimeout( this.time );                  
                    callback && callback();
                }else if( marginLeft < target ){
                        marginLeft++;
                    }else{
                        marginLeft--;
                    }
                } 
                //这里写在判断语句之外就会出一个定时器泄漏的情况
                //无论marginLeft 与 target是大于等于小于都会再次执行animate()
               //即找不到一个退出当前定时器的条件,相当于设置了一个setInterval()
                ball.style.marginLeft = marginLeft + 'px';
                animate(ball,target,callback);
            },15);

其实细想一旦setTimeout()动画需要你去清理掉定时器,那么肯定没有给出退出条件。setTimeout()理论上是仅执行一次无需清理,所以最后代码修改了一下,那就是在marginLeft != target时才再次执行animate();

注意:理解这个错误后就会明白为什么1-2-3-3-2-1不能执行了,而加入清理定时器后1-2-3-1-2-3又能执行(其实这也没有正确执行,最后一次的setTimeout依然没有被清除)
正确的代码是这样:

    function animate(ball,target,callback)
        {
            setTimeout(function(){
                var marginLeft = parseInt( ball.style.marginLeft,10 );
                if( marginLeft === target )
                {
                    callback && callback();
                }else{
                    if( marginLeft < target ){
                        marginLeft++;
                    }else{
                        marginLeft--;
                    }
                    ball.style.marginLeft = marginLeft + 'px';
                    animate(ball,target,callback);
                } 
            },15);
        }

正确执行后也就不需要什么清理定时器了。

可见在使用setTimeout()制作动画时一定要注意逻辑关系,一定要给出退出条件,不然就会出错。

点击查看更多内容
3人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消