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 相关文章推荐
JavaScript中出现乱码的处理心得
Dec 24 Javascript
基于jquery的固定表头和列头的代码
May 03 Javascript
Jquery颜色选择器ColorPicker实现代码
Nov 14 Javascript
Mac/Windows下如何安装Node.js
Nov 22 Javascript
AngularJS实现ajax请求的方法
Nov 22 Javascript
xmlplus组件设计系列之按钮(2)
Apr 26 Javascript
vue2.0项目中使用Ueditor富文本编辑器示例代码
Aug 14 Javascript
JS实现网页抢购功能(触发,终止脚本)
Nov 27 Javascript
AngularJS创建一个上传照片的指令实例代码
Feb 24 Javascript
vue对storejs获取的数据进行处理时遇到的几种问题小结
Mar 20 Javascript
Vue 实现分页与输入框关键字筛选功能
Jan 02 Javascript
JavaScript实现下拉列表
Jan 20 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
在线短消息收发的程序,不用数据库
2006/10/09 PHP
快速解决PHP调用Word组件DCOM权限的问题
2017/12/27 PHP
PHP实现的超长文本分页显示功能示例
2018/06/04 PHP
javascript firefox兼容ie的dom方法脚本
2008/05/18 Javascript
html中使用javascript调用本地程序(exe、doc等)实现代码
2013/04/26 Javascript
JavaScript中常见获取元素的方法汇总
2015/03/04 Javascript
浏览器环境下JavaScript脚本加载与执行探析之动态脚本与Ajax脚本注入
2016/01/19 Javascript
jQuery遍历DOM元素与节点方法详解
2016/04/14 Javascript
基于WebUploader的文件上传js插件
2016/08/19 Javascript
微信小程序 开发工具快捷键整理
2016/10/31 Javascript
vue基于mint-ui的城市选择3级联动的示例
2017/10/25 Javascript
vue watch监听对象及对应值的变化详解
2018/02/24 Javascript
详解Angular系列之变化检测(Change Detection)
2018/02/26 Javascript
用p5.js制作烟花特效的示例代码
2018/03/21 Javascript
jQuery实现每隔一段时间自动更换样式的方法分析
2018/05/03 jQuery
jQuery实现table表格信息的展开和缩小功能示例
2018/07/21 jQuery
vue中通过使用$attrs实现组件之间的数据传递功能
2019/09/01 Javascript
node.js +mongdb实现登录功能
2020/06/18 Javascript
小程序实现点击tab切换左右滑动
2020/11/16 Javascript
[01:47]2018年度DOTA2最佳教练-完美盛典
2018/12/16 DOTA
20个常用Python运维库和模块
2018/02/12 Python
Python 字符串与二进制串的相互转换示例
2018/07/23 Python
对Django中static(静态)文件详解以及{% static %}标签的使用方法
2019/07/28 Python
Python 批量刷博客园访问量脚本过程解析
2019/08/30 Python
Python3爬虫关于识别点触点选验证码的实例讲解
2020/07/30 Python
python中pow函数用法及功能说明
2020/12/04 Python
实习期自我鉴定
2013/10/11 职场文书
跟单文员岗位职责
2014/01/03 职场文书
三八红旗集体先进事迹材料
2014/05/22 职场文书
开服装店计划书
2014/08/15 职场文书
工地例会施工汇报材料
2014/08/22 职场文书
保密工作承诺书
2014/08/29 职场文书
会议欢迎词范文
2015/01/27 职场文书
老员工辞职信范文
2015/05/12 职场文书
奖金申请报告模板
2015/05/15 职场文书
mysql下的max_allowed_packet参数设置详解
2022/02/12 MySQL