Javascript动画的实现原理浅析


Posted in Javascript onMarch 02, 2015

假设有这样一个动画功能需求:把一个div的宽度从100px变化到200px。写出来的代码可能是这样的:

<div id="test1" style="width: 100px; height: 100px; background: blue; color: white;"></div>

function animate1(element, endValue, duration) {

    var startTime = new Date(),

        startValue = parseInt(element.style.width),

        step = 1;

    

    var timerId = setInterval(function() {

        var nextValue = parseInt(element.style.width) + step;

        element.style.width = nextValue + 'px';

        if (nextValue >= endValue) {

            clearInterval(timerId);

            // 显示动画耗时

            element.innerHTML = new Date - startTime;

        }

    }, duration / (endValue - startValue) * step);

}
animate1(document.getElementById('test1'), 200, 1000);

原理是每隔一定时间增加1px,一直到200px为止。然而,动画结束后显示的耗时却不止1s(一般是1.5s左右)。究其原因,是因为setInterval并不能严格保证执行间隔。

有没有更好的做法呢?下面先来看一道小学数学题:

A楼和B楼相距100米,一个人匀速从A楼走到B楼,走了5分钟到达目的地,问第3分钟时他距离A楼多远?

匀速运动中计算某个时刻路程的计算公式为:路程 * 当前时间 / 时间 。所以答案应为 100 * 3 / 5 = 60 。

这道题带来的启发是,某个时刻的路程是可以通过特定公式计算出来的。同理,动画过程中某个时刻的值也可以通过公式计算出来,而不是累加得出:

<div id="test2" style="width: 100px; height: 100px; background: red; color: white;"></div>

function animate2(element, endValue, duration) {

    var startTime = new Date(),

        startValue = parseInt(element.style.width);
    var timerId = setInterval(function() {

        var percentage = (new Date - startTime) / duration;
        var stepValue = startValue + (endValue - startValue) * percentage;

        element.style.width = stepValue + 'px';
        if (percentage >= 1) {

            clearInterval(timerId);

            element.innerHTML = new Date - startTime;

        }

    }, 13);

}
animate2(document.getElementById('test2'), 200, 1000);

这样改良之后,可以看到动画执行耗时最多只会有10几ms的误差。但是问题还没完全解决,在浏览器开发工具中检查test2元素可以发现,test2的最终宽度可能不止200px。仔细检查animate2函数的代码可以发现:

1.percentage的值可能大于1,可以通过Math.min限制最大值解决。
2.即使保证了percentage的值不大于1,只要endValue或startValue为小数,(endValue - startValue) * percentage的值也可能产生误差,因为Javascript小数运算的精度不够。其实我们要保证的只是最终值的准确性,所以在percentage为1的时候,直接使用endValue即可。

于是,animate2函数的代码修改为:

function animate2(element, endValue, duration) {

    var startTime = new Date(),

        startValue = parseInt(element.style.width);
    var timerId = setInterval(function() {

        // 保证百分率不大于1

        var percentage = Math.min(1, (new Date - startTime) / duration);
        var stepValue;

        if (percentage >= 1) {

            // 保证最终值的准确性

            stepValue = endValue;

        } else {

            stepValue = startValue + (endValue - startValue) * percentage;

        }

        element.style.width = stepValue + 'px';
        if (percentage >= 1) {

            clearInterval(timerId);

            element.innerHTML = new Date - startTime;

        }

    }, 13);

}

还有最后一个疑问:setInterval的间隔为何设为13ms?原因是当下显示器的刷新率一般不超过75Hz(即每秒刷新75次,也就是每隔约13ms刷新一次),把间隔跟刷新率同步效果更好。

Javascript 相关文章推荐
URL编码转换,escape() encodeURI() encodeURIComponent()
Dec 27 Javascript
JavaScript Undefined,Null类型和NaN值区别
Oct 22 Javascript
innerHTML与jquery里的html()区别介绍
Oct 12 Javascript
用Jquery.load载入页面后样式没了页面混乱的解决方法
Oct 20 Javascript
Javascript数组与字典用法分析
Dec 13 Javascript
JavaScript实现彩虹文字效果的方法
Apr 16 Javascript
不能不知道的10个angularjs英文学习网站
Mar 23 Javascript
微信小程序 Video API实例详解
Oct 02 Javascript
Bootstrap面板学习使用
Feb 09 Javascript
VSCode配置react开发环境的步骤
Dec 27 Javascript
vue 项目地址去掉 #的方法
Oct 20 Javascript
vue使用video插件vue-video-player的示例
Oct 03 Javascript
JavaScript页面模板库handlebars的简单用法
Mar 02 #Javascript
EasyUI中实现form表单提交的示例分享
Mar 01 #Javascript
EasyUI实现二级页面的内容勾选的方法
Mar 01 #Javascript
EasyUI实现第二层弹出框的方法
Mar 01 #Javascript
EasyUI,点击开启编辑框,并且编辑框获得焦点的方法
Mar 01 #Javascript
浅谈EasyUI中Treegrid节点的删除
Mar 01 #Javascript
浅谈EasyUI中编辑treegrid的方法
Mar 01 #Javascript
You might like
php格式化电话号码的方法
2015/04/24 PHP
PHP使用Memcache时模拟命名空间及缓存失效问题的解决
2016/02/27 PHP
PHP的Yii框架中移除组件所绑定的行为的方法
2016/03/18 PHP
PHP图片裁剪与缩放示例(无损裁剪图片)
2017/02/08 PHP
Ajax中的JSON格式与php传输过程全面解析
2017/11/14 PHP
PHP7数组的底层实现示例
2019/08/25 PHP
JavaScript中this关键字使用方法详解
2007/03/08 Javascript
asp 的 分词实现代码
2007/05/24 Javascript
Javascript base64编码实现代码
2011/12/02 Javascript
实现只能输入数字的input不用replace方法
2013/09/12 Javascript
使用正则表达式的格式化与高亮显示json字符串
2014/12/03 Javascript
angularjs中的e2e测试实例
2014/12/06 Javascript
JS加载器如何动态加载外部js文件
2016/05/26 Javascript
JavaScript中的各种操作符使用总结
2016/05/26 Javascript
JS针对Array的各种操作汇总
2016/11/29 Javascript
Vue.js仿微信聊天窗口展示组件功能
2017/08/11 Javascript
微信小程序日历/日期选择插件使用方法详解
2018/12/28 Javascript
JavaScript中filter的用法实例分析
2019/02/27 Javascript
vue store之状态管理模式的详细介绍
2019/06/13 Javascript
vue router总结 $router和$route及router与 router与route区别
2019/07/05 Javascript
jQuery实现轮播图效果demo
2020/01/11 jQuery
Python协程 yield与协程greenlet简单用法示例
2019/11/22 Python
python中with用法讲解
2020/02/07 Python
用python对excel进行操作(读,写,修改)
2020/12/25 Python
通过一张图教会你CSS3倒影的实现
2017/09/26 HTML / CSS
html5实现多图片预览上传及点击可拖拽控件
2018/03/15 HTML / CSS
意大利中国电子产品购物网站:Geekmall.com
2019/09/30 全球购物
部门年终奖分配方案
2014/05/07 职场文书
迎国庆演讲稿
2014/09/05 职场文书
党风廉正建设责任书
2015/01/29 职场文书
幼儿园门卫安全责任书
2015/05/08 职场文书
《跨越海峡的生命桥》教学反思
2016/02/18 职场文书
导游词之台湾阿里山
2019/10/23 职场文书
5人制售《绝地求生》游戏外挂获利500多万元 被判刑
2022/03/31 其他游戏
python多次执行绘制条形图
2022/04/20 Python
如何让你的Nginx支持分布式追踪详解
2022/07/07 Servers