最近学习JS的感悟-2(动画方面)
2013-05-03 18:58
295 查看
昨天写那篇文章花费了一个多小时,所以后面就没写了,今天继续写一点吧,主要就想说一下动画方面的内容。
W3C现在已经定义了WindowAnimationTiming interface规范,其核心方法是requestAnimactionFrame和cancelRequestAnimationFrame,在很多新浏览器中都有实现,用它可以实现动画,但是貌似兼容性不太好,特别是在天国,还有IE6扫尾,所以貌似不靠谱,不过可以做一个判定,如果支持这个规范,则使用它,否则,使用我后面讲的这种方式,不过貌似比较麻烦,所以我没有使用这种方式,如果后面有时间了,可以去写着玩儿一下。
除了这种不靠谱的方式,可能大家用的比较多的就是setInterval了,用它来完成动画是比较靠谱的。其实它的原理非常简单,就是在规定的时间内每隔一段时间执行一次回调,这个回调中可以进行一些动画的处理,比如修改元素大小,位置等。
说到这儿,就必须要提几个概念了,首先是duration(动画的执行时间),frameTime(每次执行的时间间隔),然后是FPS(每秒的帧数,也就是1000 / frameTime)。一般来说,FPS越高,动画就表现的越流畅,FPS越低,动画就越不流畅。在Jquery中,默认的frameTime为13ms,在理想情况下,FPS可以达到70多。
为什么说是理想下呢,因为JS的定时器存在精度问题,间隔不能太小,资源暂用较大的时候,这个间隔还没办法得到保障,更不靠谱的是,在一些高级浏览器中,页面不可见(如切换到其他选项卡,最小化)的时候,这个时间间隔会被自动提高,比如firefox5开始,setInterval的间隔在浏览器最小化之后至少被提高到1000ms。
好,我们来总结一下上面这一段话,也就是说,这个frameTime是不靠谱的,根本不可控的,即使你写了setInterval(xxx,20),但也不一定会是20ms执行一次。所以这里就分化出两种策略了,一种是弃时保帧,另一种是弃帧保时。所谓的弃时保帧很容易理解,我要保证执行的次数有这么多,但这样就不保证duration;所谓的弃帧保时也好理解,我保证动画在duration结束,但是不保证到底执行了多少次(理论上执行次数为duration/frameTime)。
在我写这个库的最开始,由于不知道setInterval的问题, 我采用的是弃时保帧的策略,也就是我定义了frameTime和steps,如果内部指定一个计数器,伪代码如下:
使用了弃帧保时的策略之后,整个动画的编写和之前完全不一样了,动画这一块儿看了很多qwrap的代码,这个库的具体信息可点此:http://www.qwrap.com/
之前采用的是steps,也就是必须要执行那么多帧,而现在肯定保证不了,所以,我们换一种思路,我们在动画开始的时候记录一下时间,也就是通过:
使用这个策略之后,整个代码如下:
其实最基本的动画代码非常简单,如下:
PS:这个代码是我理解动画原理的代码
![](http://my.oschina.net/js/ke/plugins/emoticons/images/13.gif)
,当时网上找的,感谢作者啊!!
之前我们的动画只能进行匀速的动画,对于复杂的动画,这样肯定满足不了要求,那么怎么做呢?
这里就需要引入算子了,具体的解释大家可以google一下哈。
具体如下:
除了这种,动画中还有动画队列,这里我就不说了。
差不多一个小时,写完收工。
W3C现在已经定义了WindowAnimationTiming interface规范,其核心方法是requestAnimactionFrame和cancelRequestAnimationFrame,在很多新浏览器中都有实现,用它可以实现动画,但是貌似兼容性不太好,特别是在天国,还有IE6扫尾,所以貌似不靠谱,不过可以做一个判定,如果支持这个规范,则使用它,否则,使用我后面讲的这种方式,不过貌似比较麻烦,所以我没有使用这种方式,如果后面有时间了,可以去写着玩儿一下。
除了这种不靠谱的方式,可能大家用的比较多的就是setInterval了,用它来完成动画是比较靠谱的。其实它的原理非常简单,就是在规定的时间内每隔一段时间执行一次回调,这个回调中可以进行一些动画的处理,比如修改元素大小,位置等。
说到这儿,就必须要提几个概念了,首先是duration(动画的执行时间),frameTime(每次执行的时间间隔),然后是FPS(每秒的帧数,也就是1000 / frameTime)。一般来说,FPS越高,动画就表现的越流畅,FPS越低,动画就越不流畅。在Jquery中,默认的frameTime为13ms,在理想情况下,FPS可以达到70多。
为什么说是理想下呢,因为JS的定时器存在精度问题,间隔不能太小,资源暂用较大的时候,这个间隔还没办法得到保障,更不靠谱的是,在一些高级浏览器中,页面不可见(如切换到其他选项卡,最小化)的时候,这个时间间隔会被自动提高,比如firefox5开始,setInterval的间隔在浏览器最小化之后至少被提高到1000ms。
好,我们来总结一下上面这一段话,也就是说,这个frameTime是不靠谱的,根本不可控的,即使你写了setInterval(xxx,20),但也不一定会是20ms执行一次。所以这里就分化出两种策略了,一种是弃时保帧,另一种是弃帧保时。所谓的弃时保帧很容易理解,我要保证执行的次数有这么多,但这样就不保证duration;所谓的弃帧保时也好理解,我保证动画在duration结束,但是不保证到底执行了多少次(理论上执行次数为duration/frameTime)。
在我写这个库的最开始,由于不知道setInterval的问题, 我采用的是弃时保帧的策略,也就是我定义了frameTime和steps,如果内部指定一个计数器,伪代码如下:
if(counter >= steps) { clearInterval(interval); } else { counter++; //动画函数 }最开始的完整代码如下:
tp.animate.base = function(elem,options,callback) { var _options = { property : "left", from : 0, to : 200, steps : 30, time : 1000, needAddPx : true, autoStart : true }; for(var name in options) { _options[name] = options[name]; } callback = callback ? callback : function(){}; var curStep = 0; var suffix = _options.needAddPx ? "px" : ""; var interval = parseFloat((_options.from - _options.to)) / parseInt(_options.steps); var intervalHandler = null; var _publicMethod = { start : function() { intervalHandler = setInterval(_animateHelper,_options.time / _options.steps); return this; }, stop : function() { clearInterval(intervalHandler); return this; }, toStart : function() { curStep = 0; elem.style[_options.property] = _options.from + suffix; return this; }, toEnd : function() { curStep = _options.steps; elem.style[_options.property] = _options.to + suffix; return this; } }; var _updatePropertyVal = null; if("opacity" === _options.property) { _updatePropertyVal = function(val) { tp.dom.opacity(elem,val); }; } else { _updatePropertyVal = function(val) { elem.style[_options.property] = val; }; } function _animateHelper() { var newVal = _options.from - (++curStep * interval); if(curStep < _options.steps) { _updatePropertyVal(newVal + suffix); } else { _updatePropertyVal(_options.to + suffix); _publicMethod.stop(); callback(elem); } } if(_options.autoStart) { _publicMethod.start(); } return _publicMethod; };这个代码当时出现了严重的抖动问题,其根本原因也是因为setInterval不准,而且IE下面有时候一个600ms的动画2m了还是没有执行完成,所以,这个直接pass掉。
使用了弃帧保时的策略之后,整个动画的编写和之前完全不一样了,动画这一块儿看了很多qwrap的代码,这个库的具体信息可点此:http://www.qwrap.com/
之前采用的是steps,也就是必须要执行那么多帧,而现在肯定保证不了,所以,我们换一种思路,我们在动画开始的时候记录一下时间,也就是通过:
var startTime = (new Date()).getTime();然后定时器执行的时候,我们再查看一下当前的时间,即currentTime,这里我们定义一个per,用这个来定义动画的执行进度,它的取值为0-1,0表示还没有开始,1代表动画执行结束。per的计算如下:
per = (currentTime - startTime) / duration;如果per大于等于1,那么说明动画结束,也就是需要clearInterval了。
使用这个策略之后,整个代码如下:
(function() { var animate = function(dur,options,animateFn) { tp.object.extend(this,options); tp.object.extend(this,{ animateFn : animateFn, dur : dur, per : 0, frameTime : options.frameTime || 13, status : 0, startDate : 0 , interval : null }); changePer(this,0); }; function changePer(aniObj,per) { aniObj.per = per; aniObj.startDate = (new Date()).getTime() - per * aniObj.dur; } function turnOn(aniObj) { aniObj.step(null); if(aniObj.isPlaying()) { aniObj.interval = window.setInterval(function() { aniObj.step(null); },aniObj.frameTime); } } function turnOff(aniObj) { window.clearInterval(aniObj.interval); } tp.object.extend(animate.prototype,{ isPlaying : function() { return this.status == 1; }, play : function() { var me = this; if(me.isPlaying()) { me.pause(); } changePer(me,0); me.status = 1; me.onplay && me.onplay(); turnOn(me); }, step : function(per) { var me = this; if(null != per) { changePer(me,per); } else { //计算出per per = ((new Date()).getTime() - me.startDate) / me.dur; me.per = per; } if(me.per > 1) { me.per = 1; } me.animateFn(me.per); me.onstep && me.onstep(); if(me.per >= 1) { me.end(); } }, end : function() { changePer(this,1); this.animateFn(1); this.status = 2; turnOff(this); this.onend && this.onend(); }, pause : function() { this.status = 4; turnOff(this); this.onpause && this.onpause(); }, resume : function() { changePer(this,this.per); this.status = 1; this.onresume && this.onresume(); turnOn(this); }, reset : function() { changePer(this,0); this.animateFn(0); this.onreset && this.onreset(); } }); tp.animate.base = animate; })();tp.object.extend可以扩展某一个对象的方法,其实作用和一般库的mix一样。
其实最基本的动画代码非常简单,如下:
<script> var timerId, startTime, frameTime = 13, dur = 3 * 1000; function animFun(time) { var per = Math.min(1.0, (new Date - startTime) / dur); if(per >= 1) { clearTimeout(timerId); } else { document.getElementById("animated").style.left = Math.round(500 * per) + "px"; } } function start() { startTime = new Date; timerId = setInterval(animFun, frameTime); } </script> <div id="animated" onclick="start()" style="position: absolute; left: 0px; padding: 50px;background: crimson; color: white">Click Me</div>
PS:这个代码是我理解动画原理的代码
![](http://my.oschina.net/js/ke/plugins/emoticons/images/13.gif)
,当时网上找的,感谢作者啊!!
之前我们的动画只能进行匀速的动画,对于复杂的动画,这样肯定满足不了要求,那么怎么做呢?
这里就需要引入算子了,具体的解释大家可以google一下哈。
具体如下:
<script> var timerId, startTime, frameTime = 13, dur = 3 * 1000; function easing(p) { return p * p; } function animFun(time) { var per = Math.min(1.0, (new Date - startTime) / dur); if(per >= 1) { clearTimeout(timerId); } else { document.getElementById("animated").style.left = Math.round(500 * easing(per)) + "px"; } } function start() { startTime = new Date; timerId = setInterval(animFun, frameTime); } </script> <div id="animated" onclick="start()" style="position: absolute; left: 0px; padding: 50px;background: crimson; color: white">Click Me</div>这里面多的代码就是:
function easing(p) { return p * p; }现在网上这种算子还蛮多的,比如KISSY:
KISSY.add('anim/easing', function () { var PI = Math.PI, pow = Math.pow, sin = Math.sin, BACK_CONST = 1.70158; var Easing = { /** * swing effect. */ swing: function (t) { return ( -Math.cos(t * PI) / 2 ) + 0.5; }, /** * Uniform speed between points. */ 'easeNone': function (t) { return t; }, /** * Begins slowly and accelerates towards end. (quadratic) */ 'easeIn': function (t) { return t * t; }, /** * Begins quickly and decelerates towards end. (quadratic) */ easeOut: function (t) { return ( 2 - t) * t; }, /** * Begins slowly and decelerates towards end. (quadratic) */ easeBoth: function (t) { return (t *= 2) < 1 ? .5 * t * t : .5 * (1 - (--t) * (t - 2)); }, /** * Begins slowly and accelerates towards end. (quartic) */ 'easeInStrong': function (t) { return t * t * t * t; }, /** * Begins quickly and decelerates towards end. (quartic) */ easeOutStrong: function (t) { return 1 - (--t) * t * t * t; }, /** * Begins slowly and decelerates towards end. (quartic) */ 'easeBothStrong': function (t) { return (t *= 2) < 1 ? .5 * t * t * t * t : .5 * (2 - (t -= 2) * t * t * t); }, /** * Snap in elastic effect. */ 'elasticIn': function (t) { var p = .3, s = p / 4; if (t === 0 || t === 1) return t; return -(pow(2, 10 * (t -= 1)) * sin((t - s) * (2 * PI) / p)); }, /** * Snap out elastic effect. */ elasticOut: function (t) { var p = .3, s = p / 4; if (t === 0 || t === 1) return t; return pow(2, -10 * t) * sin((t - s) * (2 * PI) / p) + 1; }, /** * Snap both elastic effect. */ 'elasticBoth': function (t) { var p = .45, s = p / 4; if (t === 0 || (t *= 2) === 2) return t; if (t < 1) { return -.5 * (pow(2, 10 * (t -= 1)) * sin((t - s) * (2 * PI) / p)); } return pow(2, -10 * (t -= 1)) * sin((t - s) * (2 * PI) / p) * .5 + 1; }, /** * Backtracks slightly, then reverses direction and moves to end. */ 'backIn': function (t) { if (t === 1) t -= .001; return t * t * ((BACK_CONST + 1) * t - BACK_CONST); }, /** * Overshoots end, then reverses and comes back to end. */ backOut: function (t) { return (t -= 1) * t * ((BACK_CONST + 1) * t + BACK_CONST) + 1; }, /** * Backtracks slightly, then reverses direction, overshoots end, * then reverses and comes back to end. */ 'backBoth': function (t) { var s = BACK_CONST; var m = (s *= 1.525) + 1; if ((t *= 2 ) < 1) { return .5 * (t * t * (m * t - s)); } return .5 * ((t -= 2) * t * (m * t + s) + 2); }, /** * Bounce off of start. */ bounceIn: function (t) { return 1 - Easing.bounceOut(1 - t); }, /** * Bounces off end. */ bounceOut: function (t) { var s = 7.5625, r; if (t < (1 / 2.75)) { r = s * t * t; } else if (t < (2 / 2.75)) { r = s * (t -= (1.5 / 2.75)) * t + .75; } else if (t < (2.5 / 2.75)) { r = s * (t -= (2.25 / 2.75)) * t + .9375; } else { r = s * (t -= (2.625 / 2.75)) * t + .984375; } return r; }, /** * Bounces off start and end. */ 'bounceBoth': function (t) { if (t < .5) { return Easing.bounceIn(t * 2) * .5; } return Easing.bounceOut(t * 2 - 1) * .5 + .5; } }; return Easing; });刚才我们写的tp.animate.base可以完成万能动画,我们只需要填写具体的帧动画即可。但对于颜色动画来说,还是比较麻烦的,我这儿写了一个组件,代码如下:
define("tp.animate.color",[],function() { //特殊的颜色值 var KEYWORDS = { 'black':[0, 0, 0], 'silver':[192, 192, 192], 'gray':[128, 128, 128], 'white':[255, 255, 255], 'maroon':[128, 0, 0], 'red':[255, 0, 0], 'purple':[128, 0, 128], 'fuchsia':[255, 0, 255], 'green':[0, 128, 0], 'lime':[0, 255, 0], 'olive':[128, 128, 0], 'yellow':[255, 255, 0], 'navy':[0, 0, 128], 'blue':[0, 0, 255], 'teal':[0, 128, 128], 'aqua':[0, 255, 255] }; /* 解析颜色,支持rgb和hex颜色值,特殊颜色如black */ function parseColor(color) { var r = 0, g = 0, b = 0; if(/rgb/.test(color)) { //参数为RGB模式,不做进制转换,直接截取字符串,如rgb(2,2,2) var arr = color.match(/\d+/g); r = parseInt(arr[0]); g = parseInt(arr[1]); b = parseInt(arr[2]); } else if(/#/.test(color)) { //参数为十六进制,需要进行进制转换 var len = color.length; if(7 === len) { //非简写,#ffccdd r = parseInt(color.slice(1,3),16); g = parseInt(color.slice(3,5),16); b = parseInt(color.slice(5),16); } else if(4 === len) { //简写模式 #eaf r = parseInt(color.charAt(1) + color.charAt(1),16); g = parseInt(color.charAt(2) + color.charAt(2),16); b = parseInt(color.charAt(3) + color.charAt(3),16); } else { tp.log("error format of color:" + color); } } else { //特殊颜色,如black var tmpColor = KEYWORDS[color.toLowerCase()]; if(tmpColor) { r = tmpColor[0]; g = tmpColor[1]; b = tmpColor[2]; } else { tp.log("error format of color:" + color); } } return [r,g,b]; } function setValue(ele,attr,color) { if(tp.type.isString(color)) { //直接设置如#cccccc tp.dom.css(ele,attr,color); } else { //数组,设置如rgb(22,22,22) tp.dom.css(ele,attr,"rgb(" + color.join(",") + ")"); } } //preColor为arr function getNewColor(preColor,spacing,per) { var arr = new Array(); arr[0] = Math.max(Math.min(parseInt(preColor[0] + per * spacing[0]),255),0); arr[1] = Math.max(Math.min(parseInt(preColor[1] + per * spacing[1]),255),0); arr[2] = Math.max(Math.min(parseInt(preColor[2] + per * spacing[2]),255),0); return "rgb(" + arr.join(",") + ")"; } /** * @namespace tp.animate.color * @param {Object} options * @config {Object} [ele] 动画元素 * @config {String} [from] 起始颜色值 * @config {String} [to] 结束颜色值 * @config {String} [attr] 操作的属性名 */ var color = function(options) { var fromArr = options.from ? parseColor(options.from) : parseColor(tp.dom.css(options.ele,options.attr)), toArr = parseColor(options.to); tp.object.extend(this,{ spacing : [toArr[0] - fromArr[0] , toArr[1] - fromArr[1] , toArr[2] - fromArr[2]], attr : options.attr, fromArr : fromArr, ele : options.ele }); }; tp.object.extend(color.prototype,{ action : function(per) { var newColor = getNewColor(this.fromArr,this.spacing,per); setValue(this.ele,this.attr,newColor); } }); tp.modules.add("tp.animate.color",color); });调用如下:
tp.use(["tp.animate.color","tp.animate.easing"],function(color,easing) { var _color = new color({ ele : test, attr : "backgroundColor", to : "#AACCBB" }); tp.event.on(btn,"click",function() { var _a = new tp.animate.base(900,{ dur : 150 },function(per) { _color.action(easing.backIn(per)); }); _a.play(); }); });其实颜色动画的原理也很简单,比如从一个颜色A到另一个颜色B,每一个颜色都有一个RGB值,所以可以把整个过程分开,R从A到B,G从A到B,B从A到B,而我们之前已经实现了tp.animate.base了,这里有一个per,在任意时刻,假设计算R,R差值C为B的R值减去A的R值,那么此刻的R值为:A的R值+per * C ,但是由于R的范围为0-255,所以稍微改变一下:
Math.max(Math.min(parseInt(A_R + per * C),255),0);个人感觉这个里面有点麻烦的还是颜色值的获取,而我在parseColor中也解决了这个问题,其实就是将一个颜色值如:#232322变成RGB的数组,方便后面调用而已。
除了这种,动画中还有动画队列,这里我就不说了。
差不多一个小时,写完收工。
相关文章推荐
- 最近学习JS的感悟-1
- js动画学习笔记
- Three.JS提升学习6:创建动画和移动摄像机
- 学习js感悟-js是什么
- HTML5学习之路--SVG配合js制作动画
- Three.JS学习 4:循环渲染与动画
- 学习笔记:Stage.js(又叫Cut.js)——2D canvas 开发库,游戏方面的
- three.js 学习感悟
- 最近学习感悟
- 一次面试感想+js最近学习体会
- 最近学习的感悟
- 最近学习到的一些东西的整理[技术方面]
- ReactJS学习笔记八:动画
- 今天的学习——关于JS时间获取方面的小问题
- 最近准备上linux 虚拟化的方面的学习文档
- 分享D瓜哥最近攒的资料(架构方面)、架构分析与设计、知名网站架构分析:Amazon网站架构学习总结、探索Google App Engine背后的奥秘、Facebook图片存储架构、优酷网架构、YouTube架构
- 最近一段时间学习C++的感悟
- ReactJS学习笔记:动画
- three.js学习笔记 用Tween.js做动画
- Cocos2d-js 学习(三): 简单动画使用