现在的位置: 首页 > 综合 > 正文

javascript动画浅析。

2013年03月08日 ⁄ 综合 ⁄ 共 10196字 ⁄ 字号 评论关闭

最近一直在弄手机端的游戏,接触到各种动画。加之对之前的自己那个动画类不满意,就有心想写个新的。

然后翻看各种博客,查资料。也学到一些新的东西。
动画原理

所谓的动画,就是通过一些列的运动形成的动的画面。在网页中,我们可以通过不断的改变元素的css值,来达到动的效果。

用到的公式

总距离S = 总时间T * 速度V 即: V = S/T

当前距离s = S/T * 已耗时t 即: s = S * (t/T)

即:当前距离 = 总距离 * (已耗时/总时间)

即:动画元素开始值 + (动画元素结束值 - 动画元素开始值) * (当前时间-开始时间) / (动画需要时间) + 值的格式

有了上面这些公式,我们就能利用javascript的setInterval或者setTimeout来做一个简单的动画了。

然而想要做一个动画库,就不得不考虑另外一些因素了。 比如同一个元素的动画,必须要有顺序的执行。不同元素的动画可以同步运行。

如此一来,就必须得用另外一个对象来管理这些动画了。我开始的想法是讲每个元素都放在一个数组里,用几个setInterval来循环取出数组中的动画函数依次执行。

animate1 = [{elem,fn},{elem,fn}];

animate2 = [{elem,fn},{elem,fn}];

这样就能达到,相同的元素动画,是有顺序的执行,而不同的则可以同时运行了。然后这样却存在一个问题,那就是如果超过10个元素的动画。程序就要开十个setInterval。

为了避免这样的情况发生,就在上面的基础上做了一些改进。使得,不论多少个动画。都使用一个setInterval来完成。修改后结构如下。

[
[elem,[fn,fn,fn,fn]],
[elem,[fn,fn,fn,fn]],
[elem,[fn,fn,fn,fn]]
]

这样一来,就可以用一个setInterval来完成所有动画了。 所需要做就是,循环取出elem,并执行elem后面一个元素的头一个fn,fn执行完毕后删除fn。调用下一个fn,如果fn全部为空则从大的数组中删除elem,如果elem为空时,则清楚setInterval。这样一来,逻辑上便可以走得通了。

然而动画最关键的因素还有一个,那就是缓动。 如果没有缓动,那么动画效果看起来就非常的死板。千篇一律。目前做js动画用到的缓动算法是很多的,大致分为两类。

一种是flash类,一种是prototype类。

flash的需要四个参数。分别是,

1.时间初始话的时间t

2.动画的初始值b

3.动画的结束值c

4.动画持续的时间d

下面是一个flash 类的匀速运动算法

 Linear: function(t,b,c,d){ return c*t/d + b; } 

另一种则是prototype,这一类的参数只需要一个,那就是当前时间t与持续时间d的比值 (t/d)

我采用了第二种,因为它的参数方便。也更加适合上面的动画公式,下面是一个prototype类的匀速运动算法

linear : function(t){ return t;}.

加入缓动后上面的公式变为

动画元素开始值 + (动画元素结束值 - 动画元素开始值) * 缓动函数((当前时间-开始时间) / (动画需要时间)) + 值的格式。

至此便是整个动画类设计便结束了。其中参考了一些其它人的博客,在此表示感谢!

最后,还是贴一下详细代码吧。

  1 /**
2 * create time 2012/08/29
3 * @author lynx cat.
4 * @version 0.77beta.
5 */
6
7
8 (function(win,doc){
9 var win = win || window;
10 var doc = doc || win.document,
11 pow = Math.pow,
12 sin = Math.sin,
13 PI = Math.PI,
14 BACK_CONST = 1.70158;
15
16 var Easing = {
17 // 匀速运动
18 linear : function(t){
19 return t;
20 },
21 easeIn : function (t) {
22 return t * t;
23 },
24 easeOut : function (t) {
25 return ( 2 - t) * t;
26 },
27 easeBoth : function (t) {
28 return (t *= 2) < 1 ?
29 .5 * t * t :
30 .5 * (1 - (--t) * (t - 2));
31 },
32 easeInStrong : function (t) {
33 return t * t * t * t;
34 },
35 easeOutStrong : function (t) {
36 return 1 - (--t) * t * t * t;
37 },
38 easeBothStrong: function (t) {
39 return (t *= 2) < 1 ?
40 .5 * t * t * t * t :
41 .5 * (2 - (t -= 2) * t * t * t);
42 },
43 easeOutQuart : function(t){
44 return -(pow((t-1), 4) -1)
45 },
46 easeInOutExpo : function(t){
47 if(t===0) return 0;
48 if(t===1) return 1;
49 if((t/=0.5) < 1) return 0.5 * pow(2,10 * (t-1));
50 return 0.5 * (-pow(2, -10 * --t) + 2);
51 },
52 easeOutExpo : function(t){
53 return (t===1) ? 1 : -pow(2, -10 * t) + 1;
54 },
55 swingFrom : function(t) {
56 return t*t*((BACK_CONST+1)*t - BACK_CONST);
57 },
58 swingTo: function(t) {
59 return (t-=1)*t*((BACK_CONST+1)*t + BACK_CONST) + 1;
60 },
61 sinusoidal : function(t) {
62 return (-Math.cos(t*PI)/2) + 0.5;
63 },
64 flicker : function(t) {
65 var t = t + (Math.random()-0.5)/5;
66 return this.sinusoidal(t < 0 ? 0 : t > 1 ? 1 : t);
67 },
68 backIn : function (t) {
69 if (t === 1) t -= .001;
70 return t * t * ((BACK_CONST + 1) * t - BACK_CONST);
71 },
72 backOut : function (t) {
73 return (t -= 1) * t * ((BACK_CONST + 1) * t + BACK_CONST) + 1;
74 },
75 bounce : function (t) {
76 var s = 7.5625, r;
77
78 if (t < (1 / 2.75)) {
79 r = s * t * t;
80 }
81 else if (t < (2 / 2.75)) {
82 r = s * (t -= (1.5 / 2.75)) * t + .75;
83 }
84 else if (t < (2.5 / 2.75)) {
85 r = s * (t -= (2.25 / 2.75)) * t + .9375;
86 }
87 else {
88 r = s * (t -= (2.625 / 2.75)) * t + .984375;
89 }
90
91 return r;
92 }
93 };
94
95 /**
96 * 基石 用于返回一个包含对话方法的对象
97 * @param elem
98 * @return {Object}
99 */
100
101 function catfx(elem){
102 elem = typeof elem === 'string' ? doc.getElementById(elem) : elem;
103 return new fx(elem);
104 }
105
106 /**
107 * 内部基石 用于返回一个包含对话方法的对象
108 * @param elem
109 * @return {Object}
110 */
111 function fx(elem){
112 this.elem = elem;
113 return this;
114 }
115
116 /**
117 * 基础类 包含一些基础方法,和不变量
118 */
119 var fxBase = {
120 speed : {
121 slow : 600,
122 fast : 200,
123 defaults : 400
124 },
125 fxAttrs : [],
126 fxMap:[],
127
128 /**
129 * 返回对象元素的css值
130 * @param elem
131 * @param p
132 * @return css value
133 */
134 getStyle : function(){
135 var fn = function (){};
136 if('getComputedStyle' in win){
137 fn = function(elem, p){
138 var p = p.replace(/\-(\w)/g,function(i,str){
139 return str.toUpperCase();
140 });
141 var val = getComputedStyle(elem, null)[p];
142 if(~(' '+p+' ').indexOf(' left right top bottom ') && val === 'auto'){
143 val = '0px';
144 }
145 return val;
146 }
147 }else {
148 fn = function(elem, p){
149 var p = p.replace(/\-(\w)/g,function(i,str){
150 return str.toUpperCase();
151 });
152 var val = elem.currentStyle[p];
153
154 if(~(' '+p+' ').indexOf(' width height') && val === 'auto'){
155 var rect = elem.getBoundingClientRect();
156 val = ( p === 'width' ? rect.right - rect.left : rect.bottom - rect.top ) + 'px';
157 }
158
159 if(p === 'opacity'){
160 var filter = elem.currentStyle.filter;
161 if( /opacity/.test(filter) ){
162 val = filter.match( /\d+/ )[0] / 100;
163 val = (val === 1 || val === 0) ? val.toFixed(0) : val.toFixed(1);
164 }else if( val === undefined ){
165 val = 1;
166 }
167 }
168
169 if(~(' '+p+' ').indexOf(' left right top bottom ') && val === 'auto'){
170 val = '0px';
171 }
172
173 return val;
174 }
175 }
176 return fn;
177 }(),
178
179 /**
180 * 返回对象元素的css值
181 * @param 颜色值(暂不支持red,pink,blue等英文)
182 * @return rgb(x,x,x)
183 */
184 getColor : function(val){
185 var r, g, b;
186 if(/rgb/.test(val)){
187 var arr = val.match(/\d+/g);
188 r = arr[0];
189 g = arr[1];
190 b = arr[2];
191 }else if(/#/.test(val)){
192 var len = val.length;
193 if( len === 7 ){
194 r = parseInt( val.slice(1, 3), 16);
195 g = parseInt( val.slice(3, 5), 16);
196 b = parseInt( val.slice(5), 16);
197 }
198 else if( len === 4 ){
199 r = parseInt(val.charAt(1) + val.charAt(1), 16);
200 g = parseInt(val.charAt(2) + val.charAt(2), 16);
201 b = parseInt(val.charAt(3) + val.charAt(3), 16);
202 }
203 }else{
204 return val;
205 }
206 return {
207 r : parseFloat(r),
208 g : parseFloat(g),
209 b : parseFloat(b)
210 }
211 },
212 /**
213 * 返回解析后的css
214 * @param prop
215 * @return {val:val,unit:unit}
216 */
217 parseStyle : function(prop){
218 var val = parseFloat(prop),
219 unit = prop.replace(/^[\-\d\.]+/, '');
220 if(isNaN(val)){
221 val = this.getColor(unit);
222 unit = '';
223 }
224 return {val : val, unit : unit};
225 },
226 /**
227 * 设置元素的透明度
228 * @param elem
229 * @param val
230 */
231 setOpacity : function(elem, val){
232 if( 'getComputedStyle' in win ){
233 elem.style.opacity = val === 1 ? '' : val;
234 }else{
235 elem.style.zoom = 1;
236 elem.style.filter = val === 1 ? '' : 'alpha(opacity=' + val * 100 + ')';
237 }
238 },
239 /**
240 * 设置元素的css值
241 * @param elem
242 * @param prop
243 * @param val
244 */
245 setStyle : function(elem, prop, val){
246 if(prop != 'opacity'){
247 prop = prop.replace(/\-(\w)/g,function(i,p){
248 return p.toUpperCase();
249 });
250 elem.style[prop] = val;
251 }else{
252 this.setOpacity(elem, val);
253 }
254 },
255 /**
256 * 返回解析后的prop
257 * @param prop
258 * @return {prop}
259 */
260 parseProp : function(prop){
261 var props = {};
262 for(var i in prop){
263 props[i] = this.parseStyle(prop[i].toString());
264 }
265 return props;
266 },
267 /**
268 * 修正用户的参数
269 * @param elem
270 * @param duration
271 * @param easing
272 * @param callback
273 * @return {options}
274 */
275 setOption : function(elem,duration, easing, callback){
276 var options = {};
277 var _this = this;
278 options.duration = function(duration){
279 if(typeof duration == 'number'){
280 return duration;
281 }else if(typeof duration == 'string' && _this.speed[duration]){
282 return _this.speed[duration];
283 }else{
284 return _this.speed.defaults;
285 }
286 }(duration);
287
288 options.easing = function(easing){
289 if(typeof easing == 'function'){
290 return easing;
291 }else if(typeof easing == 'string' && Easing[easing]){
292 return Easing[easing];
293 }else{
294 return Easing.linear;
295 }
296 }(easing);
297
298 options.callback = function(callback){
299 var _this = this;
300 return function (){
301 if(typeof callback == 'function'){
302 callback.call(elem);
303 }
304 }
305 }(callback)
306
307 return options;
308 },
309 /**
310 * 维护setInterval的函数,动画的启动
311 */
312 tick : function(){
313 var _this = this;
314 if(!_this.timer){
315 _this.timer = setInterval(function(){
316 for(var i = 0, len = _this.fxMap.length; i < len; i++){
317 var elem = _this.fxMap[i][0];
318 var core = _this.data(elem)[0];
319 core(elem);
320 }
321 },16);
322 }
323 },
324 /**
325 * 停止所有动画
326 */
327 stop : function(){
328 if(this.timer){
329 clearInterval(this.timer);
330 this.timer = undefined;
331 }
332 },
333 /**
334 * 存储或者拿出队列
335 * @param elem
336 */
337 data : function(elem){
338 for(var i = 0, len = this.fxMap.length; i < len; i++){
339 var data = this.fxMap[i];
340 if(elem === data[0]){
341 return data[1];
342 }
343 }
344 this.fxMap.push([elem,[]]);
345 return this.fxMap[this.fxMap.length - 1][1];
346
347 },
348 /**
349 * 删除队列
350 * @param elem
351 */
352 removeData : function(elem){
353 for(var i = 0, len = this.fxMap.length; i < len; i++){
354 var data = this.fxMap[i];
355 if(elem === data[0]){
356 this.fxMap.splice(i, 1);
357 if(this.isDataEmpty()){
358 this.stop();
359 }
360 }
361 }
362 },
363 isDataEmpty : function(){
364 return this.fxMap.length == 0;
365 }
366 }, $ = fxBase;
367
368 /**
369 * 核心对象,用于生成动画对象。
370 * @param elem
371 * @param props
372 * @param options
373 * @return {Object}
374 */
375 function fxCore(elem, props, options){
376 this.elem = elem;
377 this.props = props;
378 this.options = options;
379 this.start();
380 }
381
382 fxCore.prototype = {
383 constructor : fxCore,
384 /**
385 * 将动画函数加入到队列中,并启动动画。
386 */
387 start : function(){
388 var cores = $.data(this.elem);
389 cores.push(this.step());
390 $.tick();
391 },
392 /**
393 * 核心方法,控制每一帧元素的状态。
394 * @return function
395 */
396 step : function(){
397 var _this = this;
398 var fn = function(elem){
399 var t = Date.now() - this.startTime;
400 if(Date.now() < this.startTime + this.options.duration){
401 if(t <= 1){ t = 1;}
402 for(var i in this.target){
403 if(typeof this.source[i]['val'] === 'number'){
404 var val = parseFloat((this.source[i]['val'] + (this.target[i]['val'] - this.source[i]['val']) * this.options.easing(t / this.options.duration)).toFixed(7));
405 }else{
406 var r = parseInt(this.source[i]['val']['r'] + (this.target[i]['val']['r'] - this.source[i]['val']['r']) * this.options.easing(t / this.options.duration));
407 var g = parseInt(this.source[i]['val']['g'] + (this.target[i]['val']['g'] - this.source[i]['val']['g']) * this.options.easing(t / this.options.duration));
408 var b = parseInt(this.source[i]['val']['b'] + (this.target[i]['val']['b'] - this.source[i]['val']['b']) * this.options.easing(t / this.options.duration));
409 var val = 'rgb(' + r + ',' + g + ',' + b + ')';
410 }
411 $.setStyle(this.elem,i,val + this.source[i]['unit']);
412 }
413 }else{
414 for(var i in this.target){
415 if(typeof this.target[i]['val'] === 'number'){
416 var val = this.target[i]['val'];
417 }else{
418 var val = 'rgb(' + this.target[i]['val']['r'] + ',' + this.target[i]['val']['g'] + ',' + this.target[i]['val']['b'] + ')';
419 }
420 $.setStyle(elem,i,val + this.source[i]['unit']);
421 }
422 var cores = $.data(elem);
423 cores.shift();
424 this.options.callback();
425 if(cores.length == 0){
426 $.setStyle(elem,'overflow',this.overflow);
427 $.removeData(elem);
428 }
429 }
430 }
431 return function(elem){
432 if(!_this.startTime){
433 var source = {};
434 _this.target = _this.props;
435 for(var i in _this.props){
436 var val = $.getStyle(_this.elem, i);
437 source[i] = $.parseStyle(val);
438 }
439 _this.source = source;
440 _this.startTime = Date.now();
441 _this.overflow = $.getStyle(elem,'overflow');
442 $.setStyle(elem,'overflow','hidden');
443 }
444 fn.call(_this,elem);
445 }
446 }
447 }
448
449 /**
450 * 外部接口类。
451 */
452 fx.prototype = {
453 constructor : fx,
454 /**
455 * 动画方法
456 * @param prop
457 * @param duration
458 * @param easing
459 * @param callback
460 * @return {Object}
461 */
462 animate : function(prop, duration, easing, callback){
463

抱歉!评论已关闭.