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 相关文章推荐
对textarea框的代码调试,而且功能上使用非常方便,酷
Jun 30 Javascript
js中if语句的几种优化代码写法
Mar 12 Javascript
元素未显示设置width/height时IE中使用currentStyle获取为auto
May 04 Javascript
详解JavaScript数组的操作大全
Oct 19 Javascript
jquery插件ajaxupload实现文件上传操作
Dec 09 Javascript
bootstrap模态框垂直居中效果
Dec 03 Javascript
Node.js 异步异常的处理与domain模块解析
May 10 Javascript
Vuex 进阶之模块化组织详解
Jan 12 Javascript
js构造函数创建对象是否加new问题
Jan 22 Javascript
vue-devtools的安装步骤
Apr 23 Javascript
vue 微信扫码登录(自定义样式)
Jan 06 Javascript
原生JS实现汇率转换功能代码实例
May 13 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 imagecreatefrombmp 从BMP文件或URL新建一图像
2012/07/16 PHP
Linux环境下搭建php开发环境的操作步骤
2013/06/17 PHP
php伪静态之APACHE篇
2014/06/02 PHP
PHP下载大文件失败并限制下载速度的实例代码
2019/05/10 PHP
Thinkphp框架+Layui实现图片/文件上传功能分析
2020/02/07 PHP
jQuery 源码分析笔记(2) 变量列表
2011/05/28 Javascript
js通过元素class名字获取元素集合的具体实现
2014/01/06 Javascript
JS实现简易图片轮播效果的方法
2015/03/25 Javascript
个人网站留言页面(前端jQuery编写、后台php读写MySQL)
2016/05/03 Javascript
jQuery实现Select左右复制移动内容
2016/08/05 Javascript
关于JavaScript和jQuery的类型判断详解
2016/10/08 Javascript
js实现html table 行,列锁定的简单实例
2016/10/13 Javascript
基于JS实现的随机数字抽签实例
2016/12/08 Javascript
EasyUI Datebox 日期验证之开始日期小于结束时间
2017/05/19 Javascript
jQuery Ajax使用FormData上传文件和其他数据后端web.py获取
2017/06/11 jQuery
JavaScript实现重力下落与弹性效果的方法分析
2017/12/20 Javascript
浅谈Koa2框架利用CORS完成跨域ajax请求
2018/03/06 Javascript
vue安装和使用scss及sass与scss的区别详解
2018/10/15 Javascript
微信小程序开发搜索功能实现(前端+后端+数据库)
2020/03/04 Javascript
小程序瀑布流组件实现翻页与图片懒加载
2020/05/19 Javascript
JavaScript实现滑块验证解锁
2021/01/07 Javascript
[01:22:42]2014 DOTA2华西杯精英邀请赛 5 24 DK VS LGD
2014/05/26 DOTA
[26:40]DOTA2上海特级锦标赛A组资格赛#1 Secret VS MVP.Phx第一局
2016/02/25 DOTA
python抓取某汽车网数据解析html存入excel示例
2013/12/04 Python
Python的内存泄漏及gc模块的使用分析
2014/07/16 Python
python模拟事件触发机制详解
2018/01/19 Python
简单了解python PEP的一些知识
2019/07/13 Python
浅析python标准库中的glob
2020/03/13 Python
Python常用库Numpy进行矩阵运算详解
2020/07/21 Python
Chemist Warehouse官方海外旗舰店:澳洲第一连锁大药房
2017/08/25 全球购物
员工评语大全
2014/01/19 职场文书
应届生自荐信范文
2014/02/21 职场文书
汽车促销活动方案
2014/03/31 职场文书
购房协议书范本(无房产证)
2014/10/07 职场文书
后勤工作个人总结
2015/02/28 职场文书
NoSQL优缺点与MongoDB数据库简介
2022/06/05 MongoDB