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 相关文章推荐
Prototype使用指南之base.js
Jan 10 Javascript
javascript写的一个链表实现代码
Oct 25 Javascript
使用Microsoft Ajax Minifier减小JavaScript文件大小的方法
Apr 01 Javascript
基于jquery的loading效果实现代码
Nov 05 Javascript
hover的用法及live的用法介绍(鼠标悬停效果)
Mar 29 Javascript
table对象中的insertRow与deleteRow使用示例
Jan 26 Javascript
JavaScript函数的4种调用方法详解
Apr 22 Javascript
javascript中expression的用法整理
May 13 Javascript
基于JS分页控件实现简单美观仿淘宝分页按钮效果
Nov 07 Javascript
JS实现的简单拖拽功能示例
Mar 13 Javascript
使用mock.js随机数据和使用express输出json接口的实现方法
Jan 07 Javascript
ES6字符串的扩展实例
Dec 21 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
destoon调用自定义模板及样式的公告栏
2014/06/21 PHP
php和editplus正则表达式去除空白行
2015/04/17 PHP
php操作路径的经典方法(必看篇)
2016/10/04 PHP
微信开发之php表单微信中自动提交两次问题解决办法
2017/01/08 PHP
解析 thinkphp 框架中的部分方法
2017/05/07 PHP
Yii框架创建cronjob定时任务的方法分析
2017/05/23 PHP
基于Web标准的UI组件 — 树状菜单(2)
2006/09/18 Javascript
关闭页面时window.location事件未执行的原因分析及解决方案
2014/09/01 Javascript
Angular发布1.5正式版,专注于向Angular 2的过渡
2016/02/18 Javascript
js获取对象、数组的实际长度,元素实际个数的实现代码
2016/06/08 Javascript
JS中的==运算: [''] == false —&gt;true
2016/07/24 Javascript
解决Angular2 router.navigate刷新页面的问题
2018/08/31 Javascript
Vue作用域插槽slot-scope实例代码
2018/09/05 Javascript
VUE单页面切换动画代码(全网最好的切换效果)
2019/10/31 Javascript
利用node.js开发cli的完整步骤
2020/12/29 Javascript
[00:57]林俊杰助阵DOTA2亚洲邀请赛
2015/01/28 DOTA
[36:52]DOTA2真视界:基辅特锦赛总决赛
2017/05/21 DOTA
python opencv实现运动检测
2018/07/10 Python
详解Python装饰器
2019/03/25 Python
详解使用PyInstaller将Pygame库编写的小游戏程序打包为exe文件
2019/08/23 Python
如何基于Python获取图片的物理尺寸
2019/11/25 Python
Python文本处理简单易懂方法解析
2019/12/19 Python
Tom Dixon官网:英国照明及家具设计和制造公司
2019/03/01 全球购物
写好求职应聘自荐信的三部曲
2013/09/21 职场文书
教育学习自我评价
2014/02/03 职场文书
幼儿园保育员岗位职责
2014/04/13 职场文书
小学一年级评语大全
2014/04/22 职场文书
活动总结书
2014/05/08 职场文书
六一儿童节演讲稿
2014/05/23 职场文书
教师节学生演讲稿
2014/09/03 职场文书
党员批评与自我批评发言材料
2014/10/14 职场文书
好媳妇事迹材料
2014/12/24 职场文书
小学四年级学生评语
2014/12/26 职场文书
JavaScript实现简单图片切换
2021/04/29 Javascript
Python编解码问题及文本文件处理方法详解
2021/06/20 Python
CentOS8.4安装Redis6.2.6的详细过程
2021/11/20 Redis