MyException - 我的异常网
当前位置:我的异常网» JavaScript » 第10三章 动画引擎

第10三章 动画引擎

www.MyException.Cn  网友分享于:2015-08-24  浏览:0次
第十三章 动画引擎

动画是我们眼睛中的残影,叫视觉暂留现象。这里有两个关键字,差异快速

在网页中,扩展样式的任务早已经交由css处理,让javascript第一次拥有视觉处理的api,setTimeout与setInterval早在css诞生前就已经出现。


一:动画的原理

在标准浏览器中,可计算的样式基本浏览器已经为你转化好,比如width,height,margin-x,border-x-width,padding-x.这些样式单位为px,color和background-color则被分解成RGB,这个很容易被格式化为一个数组,透明度自不用说。

不过css3新引进的变形样式transfrom,一种是面向程序员,是rotate()/skew()/scale()/translate(),分别有x, y之分,如rotateX(),rotateY(),依次类推;

一种是面向计算机,传入矩阵进去,matrix()。无论传什么,都会转成矩阵。

如果传承是旧版本的IE,就得自己转换了。比如你原来的单位是em,currentStyle就会返回em,原来填充的颜色是red,它不会返回rgb(255,0,0)

因此,动画引擎的第一步就是设法获得元素的精确样式,这个在第九章:样式模块已经介绍,给出的$.css方法基本上除了颜色值都是已经转换好的。

现在我们尝试让一个方块运动起来,简单说就是变化位置。一般来说,我们变化一个元素的top,left即可,当然也可以设置margin,这里只说元素定位的移动方法,要取得元素的left值,直接可以使用getComputedStyle,然后让用户传值改变值。就是我们需要一点点的变动值的大小。另外还要涉及到时长,就是动画的总共执行时间,就是执行的总时间,另外一个就是每次变动的相隔时间这个通常由引擎来决定,当然也可以暴露出来,它有个学名叫FPS

fps通俗的来说,叫刷新率,在1秒内更新多少次画面,根据人视觉停留效应,停留多少秒最合适呢?我们不但要照顾人的眼睛,还要顾及下显示器的显示速度与浏览器的渲染速度 ,根据国外的统计,25毫秒为最佳数值。

因此,我们创建一个新页面,里边有一个方块,它位于这个轨道上,它从一端到另外一端,动画时间为2秒,fps为30帧。

<style type="text/css">
    #way{width:800px;height:100px;background:#e8e8ff;position:relative;}
    #move{position:absolute;left:0px;width:100px;height:100px;background:#a9ea00;}
</style>
<div id="way">
    <div id="move"></div>
</div>

<script type="text/javascript">
    window.onload = function () {
        var el = document.getElementById("move");
        var parent = document.getElementById("way");
        var distance = parent.offsetWidth - el.offsetWidth; //总移动距离
        
        var bengin = parseFloat(window.getComputedStyle(el, null).left) //开始位置
        console.log(bengin)
        var end = bengin + distance; //结束位置
        var fps = 30; //刷新率
        var interval = 1000/fps ; //相隔多少ms刷新一次
        var duration = 2000  ; //时长
        var times = duration /1000 * fps ; //一共刷新的次数
        var step = distance /times; //每次移动的距离。

        el.onclick = function() {
            var now = new Date();
            var id = setInterval(function(){
                if (bengin >= end) {
                    el.style.left = end + "px";
                    clearInterval(id)
                    console.log(new Date - now)
                } else {
                    bengin += step;
                    el.style.left = bengin + "px"
                }
            },interval)
        }

    }
</script>


上面,我们用了最简单累加来实现,现在我们改写下,加入进度这个变量,让其更具有广泛性。

        el.onclick = function(){
            var benginTime = new Date();
            var id = setInterval(function(){
                var t = new Date - benginTime; //当时已经用到的时间
                if (t >= duration) {
                    el.style.left = end + "px";
                    clearInterval(id);
                    console.log(t)
                } else {
                    var per = t / duration; //当前进度
                    el.style.left = bengin + per * distance + "px";
                }
            },interval)
        }

如果我们能随意控制per这个数值,那么就能轻易实现加速减速,于是,发明了缓动公式,所谓的缓动公式,就是来自数学上的三角函数,二次项方程式,高阶方程式。它们最初是由flash界的Robert Penner整理而来(http://robertpenner.com/easing/)。

有了缓动公式,我们就能轻松的模拟加速,减速,急刹车,重力,弹簧,来回摆动等效果。

二,缓动公式

经过这么多的发展,缓动公式各项规范都稳定下来,虽然现在还有人源源不断的发掘新公式,但一般业务中,绝对数人选择使用默认的easein,linear.因此,对库的大小有顾虑,那么久将它们独立成一个模块吧。

现在所有的缓动公式,基本上除了linear外(但也被称为easeNone),它们都以ease开头命名,添加三种后缀,In表示加速,Out表示减速,InOut表示加速到中途又开始减速,于是就有了easeIn,easeOut,easeInOut之分,如果单是这样命名,说明它们没有介入高阶函数与三角函数。linear就是匀速。

然后在以实现的方式与指数或开根进行区分。Sine表示由三角函数实现Quad是二次方Cubic是三次方Quart是四次方Quint是五次方Circ使用开平方根的Match.sqit,Expo使用开立方根的Math.pow,Elastic则是结合三角函数与开立三方根的初级弹簧效果Back是使用一个1.70158常数来计算的回退效果Bounce则是高级弹簧效果

http://hosted.zeh.com.br/tweener/docs/en-us/misc/transitions.html(有更多的ease效果)

这些我们可以在AS库,jQuery.easing.js,mootools等库里拔下来,基本上大同小异。其中,jq的最规范,mootools使用循环生成的方式实现,代码最简,读者像实现自己的动画库,可以参考这两者

 jQuery标准库里只有这两条

    linear : function(p){
        return p
    },
    swing: function(p){
        return 0.5 - Math.cos{p*Math.PI} / 2;
    }
    //很显然与其它库的缓动参数兴盛不一样
    var easing = {
        esaeInQuad : function (t, b, c, d){
            return c * (t /= d) * t + b;
        },
        esaeOutQuad : function(t, b, c, d){
            return -c (t /= d) * (t - 2) + b; 
        },
        esaeInOutQuad : function(t, b, c, d){
            if ((t /= d /2 ) < 1) 
                return c / 2 * t * t + b;
            return -c / 2 * ((--t) * (t - 2) - 1) + b
        }
        //...
    }

这是因为jQuery在上面的计算过程中已经做了这一步,我们看看四个参数分别代表什么:
T: timestamap, 指缓动效果开始执行到当前帧所经过的时间段,单位ms
B: beginning val 起始值
C: change, 要变化的总量
D: duration ,动画持续的时间

返回的是直接可用的数值,或许我们只要加个单位进行赋值就行了
而jQuery那一个参数的风格,其实是当前时间减去动画开始时间除以总时间的比值,一个0到1的小数,它用于乘以总变化量,然后加上起始值,就是现在此样式的情况。

下面是缓动应用,结合上例子:

    window.onload = function () {
        var el = document.getElementById("move");
        var parent = document.getElementById("way");
        var change = parent.offsetWidth - el.offsetWidth;
        //var distance = parent.offsetWidth - el.offsetWidth; //总移动距离
        
        var bengin = parseFloat(window.getComputedStyle(el, null).left) //开始位置
        console.log(bengin)
        var end = bengin + change; //结束位置
        var fps = 30; //刷新率
        var interval = 1000/fps ; //相隔多少ms刷新一次
        var duration = 2000  ; //时长
        var bounce = function(per) {
            if (per < (1 / 2.75)) {
                return (7.5625 * per * per);
            } else if (per < (2 / 2.75)) {
                return (7.5625 * (per -= (1.5 / 2.75)) * per + .75)
            } else if (per < (2.5 / 2.75)) {
                return (7.5625 * (per -= (2.25 / 2.75))* per + .9375);
            } else {
                return (7.5625 * (per -= (2.625 / 2.75)) * per + .984375);
            }
        }
        el.onclick = function(){
            var benginTime = new Date()
            var id = setInterval(function(){
                var per  = (new Date - benginTime) / duration; //进度
                if (per >= 1) {
                    el.style.left = end + "px";
                    clearInterval(id)
                } else {
                    el.style.left = bengin + bounce(per) * change + "px";
                }
            },interval)
        }

    }


三,API设计

现在我们看如何写动画引擎。由于选择器的流行,注定这个API到用里也是集化操作,一下子处理N个元素。但这也无所谓,关键是函数名与如何传参。

jQuery的API易用性很好,我们看一下它的animate。它有两种用法。

    .animate(proprtties[,duration][,easing][,complete])
    .animate(proprtties,options)

第一个参数恒为要进行动画的属性映射,在第一种情况下,其它参数都是可选的,因为duration除了show,fast,default这三个字符串就是数字,esaing为特殊的缓动公式的名字,complete的函数,options是对象。不过尽管说的轻松,jQuery在这里也化了几十行进行转换,最后转换两个对象的形式与css3keyframe animation的定义此吻合。

我们还是利用上例子的方块,价个类名,就能看到效果了。

    #way{width:800px;height:100px;background:#e8e8ff;position:relative;}
    #move{position:absolute;left:0px;width:100px;height:100px;background:#a9ea00;}
    .animate{
        animation-duration:3s;
        animation-name:slidein;
        animation-timing-function:ease-in-out;
        animation-fill-mode:forwards;
    }
    @keyframes slidein{
        from {left:0%;background:white;}
        to {left:700px;background:red;}
    }

这个动画分为两部分:一个是普通样式规则,用于描述动画所需的时长,缓动公式,结束后保留状态,重复多少次,及关键帧的动画引用名字(slidein),第二个是关键帧的规则。这里只有两个关键帧,实际上可以插入更多,最开始与结尾可以 用from和to命名,其实当你用javascript对应jq的anmimate方法的第一个参数。至于初始值,浏览器会自动计算,如果我们使用纯javascript实现方式,这个需要我们自己来计算了。动画引擎的强大与否,取决于css模块的强大。anmimate方法的第二个参数等对应animation-duration,animation-timing-function, anmation-fill-mode.

此外,jQuery还提供一个queue参数,目的让作用于同一个元素的动画进行排队,在处理完这个在处理后一个,要不,同时用于浏览器的定时器就太多了。加之集化操作,会把它放大化,队列的重要性就更突出,为此jQuery把这个参数默认为true.

从实现上,jQuery是放在元素对应的缓存体上,里边是一个promise对象,complete之后,会自动弹出下一个动画对象。所有动画对象都有自己的setInterval驱动,在YUI,kissy则有一个中央队列,所有不排队的动画全部放在这个数组中,然后有一个setInterval来驱动它们,排队的动画作为它的兄弟属性而存在(有的中央队列可能是一条二维数组),当前面的动画执行完,后面的动画就翻身。

四,requestAnimationFrame

如果一个页面运行许多定时器,那么你无论怎么优化,最后肯定是超过指定的时间才能完成动画,定时器越多,延迟越严重。为此,YUI,kissy等采用中央队列的方式,将定时器减少至一个,浏览器厂商firefox在4.0就推出mozRequestAnimationFrame,由于浏览器在维护队列,它内部掌握DOM渲染,事件队列等情况,所以大抵能保证fps在60左右。

谷歌发现这个思路不错,整合到自己的chrome中去,相对而言,精简很多。与最后定案的标准相差很小。webkitRequestAnimationFrame有点像定时器,第一个是回调,第二个可选,传执行动画的元素节点进去,返回一个ID,然后允许像clearTimeout一样有个weibkitCancelRequest-AnimationFrame函数进行中止动画。不过名字有点长了,后来模仿firfox一样使用cancelAnimationFrame,不同的只是私有前缀。

<script type="text/javascript">
    // chrome 10-23
    var startTime, 
        duration = 3000;
    function animate(now) {
        var per = (now - startTime) / duration;
        if (per >= 1) {
            window.webkitCancelRequestAnimationFrame(requestID);
        } else {
            document.getElementById("test").style.left = Math.round(600 * per) + "px";
            window.webkitRequestAnimationFrame(animate);//不断地归抵调用animate
        }
    }
    function start(){
        startTime = Date.now();
        requestID = window.webkitRequestAnimationFrame(animate)
    }
</script>

<button onclick="start()">click</button>
<div id="test" style="position:absolute;left:10px;background:red">go</div>

IE与Opera起步最晚,IE到10才开始支持,不过这时标准已经形成,没有私有前缀,没有兼容性问题。

网上有很多非常不负责任的写法,因此,总结过很多写法,这里就不写出了。

这里参考司徒正美,网友屈屈,月影的版本改进。

    //司徒正美从,网友屈屈,月影的版本改进。
    function getAnimationFrame(){
        //不存在msRequestAnimationFrame,IE10和chrome24直接使用requestAnimationFrame
        if (window.requestAnimationFrame) {
            return {
                request:requestAnimationFrame,
                cancel:cancelRequestAnimationFrame
            }
            //firefox11没有实现cancelRequestAnimationFrame
            //并且mozRequestAnimationFrame与标准出入过大
        } else if (window.mozCancelRequestAnimationFrame && window.mozCancelAnimationFrame) {
            return {
                request:mozRequestAnimationFrame,
                cancel:mozCancelAnimationFrame
            }
        } else if (window.webkitRequestAnimationFrame && webkitRequestAnimationFrame(String)) {
            return { //修正某个特异的webkit下没有time的参数
                request:function(callback) {
                    return window.webkitRequestAnimationFrame(
                        function(){
                            return callback(new Date - 0);
                        });
                },
                cancel:window.webkitCancelAnimationFrame || window.webkitCancelRequestAnimationFrame
            }
        } else {
            var millisec = 25; //40fps
            var callbacks = [];
            var id = 0, cursor = 0;
            function playAll(){
                var cloned = callbacks.slice(0);
                cursor += callbacks.length;
                callbacks.length = 0 ;//清空队列
                for (var i = 0; callback; callback = cloned[i++]) {
                    if (callback !== "cancelled") {
                        callback(new Date - 0);
                    }
                }
            }
            window.setInterval(playAll, millisec) ;
            return {
                request:function(handler){
                    callbacks.push(handler);
                    return id++;
                },
                cancel: function(id){
                    callbacks[id - cursor] = "cancelled"
                }
            };
        }
    }

当然,requestAnimationFrame不是没有缺点,它不能控制fps,比如我们做一些慢动作,许多回调都是做无用功。另外一个极端,在动作,枪战等动态场景,如果帧数不够高,换就会发虚,模糊。利用原始的setTimeout(在IE9 10 firefox10,chrome等浏览器中,它的最短时隔已经压缩到4ms,能轻松跑100帧以上的动画),让动画更逼真,特写镜头丝丝入扣。

另外我们可以尝试postMessage这个异步方法,能实现超高度的动画(IE10 有setImmediate,速度也相当不错)。
(实验略。。)

视电脑性能而言,结果大致如下

类型 setTimeout requestAnimationFrame loop postMessage
平均帧数 200 60 200-300 900-1000

微软官方,也放出使用高性能异步的方法setImmediate,与原始的setTimeout的对比实验,流程度很好。

在现实中,尤其是游戏中的,结合多种异步API是很有必要的。如作为背景的树木,流水,NPC用requestAnimationFrame实现,而玩家的角色,由于需要点击,再配合速度,体力,耐力等元素,其走路的速度是可变的,用setTimeout实现比较合适。一些非常炫酷的动画,可能就需要postMessage,image.one, setImmediate, MessageChancel等API.

 (本章完结

上一章:第十二章:异步处理 下一章:第十四章: 插件化

文章评论

如何成为一名黑客
如何成为一名黑客
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
编程语言是女人
编程语言是女人
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
鲜为人知的编程真相
鲜为人知的编程真相
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
为什么程序员都是夜猫子
为什么程序员都是夜猫子
老程序员的下场
老程序员的下场
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
代码女神横空出世
代码女神横空出世
 程序员的样子
程序员的样子
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
每天工作4小时的程序员
每天工作4小时的程序员
我是如何打败拖延症的
我是如何打败拖延症的
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
总结2014中国互联网十大段子
总结2014中国互联网十大段子
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
程序员的鄙视链
程序员的鄙视链
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
程序员应该关注的一些事儿
程序员应该关注的一些事儿
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
中美印日四国程序员比较
中美印日四国程序员比较
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
我的丈夫是个程序员
我的丈夫是个程序员
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
那些争议最大的编程观点
那些争议最大的编程观点
程序员都该阅读的书
程序员都该阅读的书
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
程序员必看的十大电影
程序员必看的十大电影
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
漫画:程序员的工作
漫画:程序员的工作
一个程序员的时间管理
一个程序员的时间管理
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
Java程序员必看电影
Java程序员必看电影
程序员和编码员之间的区别
程序员和编码员之间的区别
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有