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 21 Javascript
浅谈JavaScript中setInterval和setTimeout的使用问题
Aug 01 Javascript
jQuery实现元素拖拽并cookie保存顺序的方法
Feb 20 Javascript
javascript封装addLoadEvent实现页面同时加载执行多个函数的方法
Jul 25 Javascript
jQuery日期范围选择器附源码下载
May 23 jQuery
深入理解ES6学习笔记之块级作用域绑定
Aug 19 Javascript
监听element-ui table滚动事件的方法
Mar 26 Javascript
利用weixin-java-miniapp生成小程序码并直接返回图片文件流的方法
Mar 29 Javascript
layui问题之自动滚动二级iframe页面到指定位置的方法
Sep 18 Javascript
Node.js控制台彩色输出的方法与原理实例详解
Dec 01 Javascript
JavaScript碰撞检测原理及其实现代码
Mar 12 Javascript
微信小程序实现列表的横向滑动方式
Jul 15 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获取文件的MD5值并判断是否被修改的例子
2014/06/19 PHP
php读取torrent种子文件内容的方法(测试可用)
2016/05/03 PHP
PHP长连接实现与使用方法详解
2018/02/11 PHP
jCallout 轻松实现气泡提示功能
2013/09/22 Javascript
CSS3,HTML5和jQuery搜索框集锦
2014/12/02 Javascript
jquery实现公告翻滚效果
2015/02/27 Javascript
this,this,再次讨论javascript中的this,超全面(经典)
2016/01/05 Javascript
判断数组的最佳方法(推荐)
2016/10/11 Javascript
详解jQuery选择器
2016/12/21 Javascript
基于bootstrap风格的弹框插件
2016/12/28 Javascript
Vue 将后台传过来的带html字段的字符串转换为 HTML
2018/03/29 Javascript
Vue中Quill富文本编辑器的使用教程
2018/09/21 Javascript
解决vue axios的封装 请求状态的错误提示问题
2018/09/25 Javascript
nodejs中函数的调用实例详解
2018/10/31 NodeJs
微信小程序开发的基本流程步骤
2019/01/31 Javascript
vue2.0中set添加属性后视图不能更新的解决办法
2019/02/22 Javascript
vue2.x 通过后端接口代理,获取qq音乐api的数据示例
2019/10/30 Javascript
JavaScript实现答题评分功能页面
2020/06/24 Javascript
详解JavaScript作用域、作用域链和闭包的用法
2020/09/03 Javascript
小程序实现tab标签页
2020/11/16 Javascript
[55:03]LGD vs EG 2018国际邀请赛小组赛BO2 第二场 8.17
2018/08/18 DOTA
Python中支持向量机SVM的使用方法详解
2017/12/26 Python
python检测IP地址变化并触发事件
2018/12/26 Python
vscode 配置 python3开发环境的方法
2019/09/19 Python
Python连接Impala实现步骤解析
2020/08/04 Python
Elemis美国官网:英国的第一豪华护肤品牌
2018/03/15 全球购物
英国休闲奢华的缩影:Crew Clothing
2019/05/05 全球购物
如何提高SQL Server的安全性
2016/07/25 面试题
建筑工程技术应届生自荐信
2013/09/27 职场文书
给水排水工程专业毕业生推荐信
2013/10/28 职场文书
《春雨》教学反思
2014/04/24 职场文书
健康家庭事迹材料
2014/05/02 职场文书
2014年实习期工作总结
2014/11/27 职场文书
2015年基层党组织公开承诺书
2015/01/21 职场文书
2016年艾滋病宣传活动总结
2016/04/01 职场文书
微信小程序纯CSS实现无限弹幕滚动效果
2022/09/23 HTML / CSS