javascript动画浅析


Posted in Javascript onAugust 30, 2012

动画原理

所谓的动画,就是通过一些列的运动形成的动的画面。在网页中,我们可以通过不断的改变元素的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;}.
加入缓动后上面的公式变为
动画元素开始值 + (动画元素结束值 - 动画元素开始值) * 缓动函数((当前时间-开始时间) / (动画需要时间)) + 值的格式。
至此便是整个动画类设计便结束了。其中参考了一些其它人的博客,在此表示感谢!
最后,还是贴一下详细代码吧。

/** 
* create time 2012/08/29 
* @author lynx cat. 
* @version 0.77beta. 
*/ (function(win,doc){ 
var win = win || window; 
var doc = doc || win.document, 
pow = Math.pow, 
sin = Math.sin, 
PI = Math.PI, 
BACK_CONST = 1.70158; 
var Easing = { 
// 匀速运动 
linear : function(t){ 
return t; 
}, 
easeIn : function (t) { 
return t * t; 
}, 
easeOut : function (t) { 
return ( 2 - t) * t; 
}, 
easeBoth : function (t) { 
return (t *= 2) < 1 ? 
.5 * t * t : 
.5 * (1 - (--t) * (t - 2)); 
}, 
easeInStrong : function (t) { 
return t * t * t * t; 
}, 
easeOutStrong : function (t) { 
return 1 - (--t) * t * t * t; 
}, 
easeBothStrong: function (t) { 
return (t *= 2) < 1 ? 
.5 * t * t * t * t : 
.5 * (2 - (t -= 2) * t * t * t); 
}, 
easeOutQuart : function(t){ 
return -(pow((t-1), 4) -1) 
}, 
easeInOutExpo : function(t){ 
if(t===0) return 0; 
if(t===1) return 1; 
if((t/=0.5) < 1) return 0.5 * pow(2,10 * (t-1)); 
return 0.5 * (-pow(2, -10 * --t) + 2); 
}, 
easeOutExpo : function(t){ 
return (t===1) ? 1 : -pow(2, -10 * t) + 1; 
}, 
swingFrom : function(t) { 
return t*t*((BACK_CONST+1)*t - BACK_CONST); 
}, 
swingTo: function(t) { 
return (t-=1)*t*((BACK_CONST+1)*t + BACK_CONST) + 1; 
}, 
sinusoidal : function(t) { 
return (-Math.cos(t*PI)/2) + 0.5; 
}, 
flicker : function(t) { 
var t = t + (Math.random()-0.5)/5; 
return this.sinusoidal(t < 0 ? 0 : t > 1 ? 1 : t); 
}, 
backIn : function (t) { 
if (t === 1) t -= .001; 
return t * t * ((BACK_CONST + 1) * t - BACK_CONST); 
}, 
backOut : function (t) { 
return (t -= 1) * t * ((BACK_CONST + 1) * t + BACK_CONST) + 1; 
}, 
bounce : 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; 
} 
}; 
/** 
* 基石 用于返回一个包含对话方法的对象 
* @param elem 
* @return {Object} 
*/ 
function catfx(elem){ 
elem = typeof elem === 'string' ? doc.getElementById(elem) : elem; 
return new fx(elem); 
} 
/** 
* 内部基石 用于返回一个包含对话方法的对象 
* @param elem 
* @return {Object} 
*/ 
function fx(elem){ 
this.elem = elem; 
return this; 
} 
/** 
* 基础类 包含一些基础方法,和不变量 
*/ 
var fxBase = { 
speed : { 
slow : 600, 
fast : 200, 
defaults : 400 
}, 
fxAttrs : [], 
fxMap:[], 
/** 
* 返回对象元素的css值 
* @param elem 
* @param p 
* @return css value 
*/ 
getStyle : function(){ 
var fn = function (){}; 
if('getComputedStyle' in win){ 
fn = function(elem, p){ 
var p = p.replace(/\-(\w)/g,function(i,str){ 
return str.toUpperCase(); 
}); 
var val = getComputedStyle(elem, null)[p]; 
if(~(' '+p+' ').indexOf(' left right top bottom ') && val === 'auto'){ 
val = '0px'; 
} 
return val; 
} 
}else { 
fn = function(elem, p){ 
var p = p.replace(/\-(\w)/g,function(i,str){ 
return str.toUpperCase(); 
}); 
var val = elem.currentStyle[p]; 
if(~(' '+p+' ').indexOf(' width height') && val === 'auto'){ 
var rect = elem.getBoundingClientRect(); 
val = ( p === 'width' ? rect.right - rect.left : rect.bottom - rect.top ) + 'px'; 
} 
if(p === 'opacity'){ 
var filter = elem.currentStyle.filter; 
if( /opacity/.test(filter) ){ 
val = filter.match( /\d+/ )[0] / 100; 
val = (val === 1 || val === 0) ? val.toFixed(0) : val.toFixed(1); 
}else if( val === undefined ){ 
val = 1; 
} 
} 
if(~(' '+p+' ').indexOf(' left right top bottom ') && val === 'auto'){ 
val = '0px'; 
} 
return val; 
} 
} 
return fn; 
}(), 
/** 
* 返回对象元素的css值 
* @param 颜色值(暂不支持red,pink,blue等英文) 
* @return rgb(x,x,x) 
*/ 
getColor : function(val){ 
var r, g, b; 
if(/rgb/.test(val)){ 
var arr = val.match(/\d+/g); 
r = arr[0]; 
g = arr[1]; 
b = arr[2]; 
}else if(/#/.test(val)){ 
var len = val.length; 
if( len === 7 ){ 
r = parseInt( val.slice(1, 3), 16); 
g = parseInt( val.slice(3, 5), 16); 
b = parseInt( val.slice(5), 16); 
} 
else if( len === 4 ){ 
r = parseInt(val.charAt(1) + val.charAt(1), 16); 
g = parseInt(val.charAt(2) + val.charAt(2), 16); 
b = parseInt(val.charAt(3) + val.charAt(3), 16); 
} 
}else{ 
return val; 
} 
return { 
r : parseFloat(r), 
g : parseFloat(g), 
b : parseFloat(b) 
} 
}, 
/** 
* 返回解析后的css 
* @param prop 
* @return {val:val,unit:unit} 
*/ 
parseStyle : function(prop){ 
var val = parseFloat(prop), 
unit = prop.replace(/^[\-\d\.]+/, ''); 
if(isNaN(val)){ 
val = this.getColor(unit); 
unit = ''; 
} 
return {val : val, unit : unit}; 
}, 
/** 
* 设置元素的透明度 
* @param elem 
* @param val 
*/ 
setOpacity : function(elem, val){ 
if( 'getComputedStyle' in win ){ 
elem.style.opacity = val === 1 ? '' : val; 
}else{ 
elem.style.zoom = 1; 
elem.style.filter = val === 1 ? '' : 'alpha(opacity=' + val * 100 + ')'; 
} 
}, 
/** 
* 设置元素的css值 
* @param elem 
* @param prop 
* @param val 
*/ 
setStyle : function(elem, prop, val){ 
if(prop != 'opacity'){ 
prop = prop.replace(/\-(\w)/g,function(i,p){ 
return p.toUpperCase(); 
}); 
elem.style[prop] = val; 
}else{ 
this.setOpacity(elem, val); 
} 
}, 
/** 
* 返回解析后的prop 
* @param prop 
* @return {prop} 
*/ 
parseProp : function(prop){ 
var props = {}; 
for(var i in prop){ 
props[i] = this.parseStyle(prop[i].toString()); 
} 
return props; 
}, 
/** 
* 修正用户的参数 
* @param elem 
* @param duration 
* @param easing 
* @param callback 
* @return {options} 
*/ 
setOption : function(elem,duration, easing, callback){ 
var options = {}; 
var _this = this; 
options.duration = function(duration){ 
if(typeof duration == 'number'){ 
return duration; 
}else if(typeof duration == 'string' && _this.speed[duration]){ 
return _this.speed[duration]; 
}else{ 
return _this.speed.defaults; 
} 
}(duration); 
options.easing = function(easing){ 
if(typeof easing == 'function'){ 
return easing; 
}else if(typeof easing == 'string' && Easing[easing]){ 
return Easing[easing]; 
}else{ 
return Easing.linear; 
} 
}(easing); 
options.callback = function(callback){ 
var _this = this; 
return function (){ 
if(typeof callback == 'function'){ 
callback.call(elem); 
} 
} 
}(callback) 
return options; 
}, 
/** 
* 维护setInterval的函数,动画的启动 
*/ 
tick : function(){ 
var _this = this; 
if(!_this.timer){ 
_this.timer = setInterval(function(){ 
for(var i = 0, len = _this.fxMap.length; i < len; i++){ 
var elem = _this.fxMap[i][0]; 
var core = _this.data(elem)[0]; 
core(elem); 
} 
},16); 
} 
}, 
/** 
* 停止所有动画 
*/ 
stop : function(){ 
if(this.timer){ 
clearInterval(this.timer); 
this.timer = undefined; 
} 
}, 
/** 
* 存储或者拿出队列 
* @param elem 
*/ 
data : function(elem){ 
for(var i = 0, len = this.fxMap.length; i < len; i++){ 
var data = this.fxMap[i]; 
if(elem === data[0]){ 
return data[1]; 
} 
} 
this.fxMap.push([elem,[]]); 
return this.fxMap[this.fxMap.length - 1][1]; 
}, 
/** 
* 删除队列 
* @param elem 
*/ 
removeData : function(elem){ 
for(var i = 0, len = this.fxMap.length; i < len; i++){ 
var data = this.fxMap[i]; 
if(elem === data[0]){ 
this.fxMap.splice(i, 1); 
if(this.isDataEmpty()){ 
this.stop(); 
} 
} 
} 
}, 
isDataEmpty : function(){ 
return this.fxMap.length == 0; 
} 
}, $ = fxBase; 
/** 
* 核心对象,用于生成动画对象。 
* @param elem 
* @param props 
* @param options 
* @return {Object} 
*/ 
function fxCore(elem, props, options){ 
this.elem = elem; 
this.props = props; 
this.options = options; 
this.start(); 
} 
fxCore.prototype = { 
constructor : fxCore, 
/** 
* 将动画函数加入到队列中,并启动动画。 
*/ 
start : function(){ 
var cores = $.data(this.elem); 
cores.push(this.step()); 
$.tick(); 
}, 
/** 
* 核心方法,控制每一帧元素的状态。 
* @return function 
*/ 
step : function(){ 
var _this = this; 
var fn = function(elem){ 
var t = Date.now() - this.startTime; 
if(Date.now() < this.startTime + this.options.duration){ 
if(t <= 1){ t = 1;} 
for(var i in this.target){ 
if(typeof this.source[i]['val'] === 'number'){ 
var val = parseFloat((this.source[i]['val'] + (this.target[i]['val'] - this.source[i]['val']) * this.options.easing(t / this.options.duration)).toFixed(7)); 
}else{ 
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)); 
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)); 
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)); 
var val = 'rgb(' + r + ',' + g + ',' + b + ')'; 
} 
$.setStyle(this.elem,i,val + this.source[i]['unit']); 
} 
}else{ 
for(var i in this.target){ 
if(typeof this.target[i]['val'] === 'number'){ 
var val = this.target[i]['val']; 
}else{ 
var val = 'rgb(' + this.target[i]['val']['r'] + ',' + this.target[i]['val']['g'] + ',' + this.target[i]['val']['b'] + ')'; 
} 
$.setStyle(elem,i,val + this.source[i]['unit']); 
} 
var cores = $.data(elem); 
cores.shift(); 
this.options.callback(); 
if(cores.length == 0){ 
$.setStyle(elem,'overflow',this.overflow); 
$.removeData(elem); 
} 
} 
} 
return function(elem){ 
if(!_this.startTime){ 
var source = {}; 
_this.target = _this.props; 
for(var i in _this.props){ 
var val = $.getStyle(_this.elem, i); 
source[i] = $.parseStyle(val); 
} 
_this.source = source; 
_this.startTime = Date.now(); 
_this.overflow = $.getStyle(elem,'overflow'); 
$.setStyle(elem,'overflow','hidden'); 
} 
fn.call(_this,elem); 
} 
} 
} 
/** 
* 外部接口类。 
*/ 
fx.prototype = { 
constructor : fx, 
/** 
* 动画方法 
* @param prop 
* @param duration 
* @param easing 
* @param callback 
* @return {Object} 
*/ 
animate : function(prop, duration, easing, callback){ 
if(arguments.length == 3 && typeof easing === 'function'){ //多数时候用户第三个参数是回调 
callback = easing; 
easing = undefined; 
} 
var props = $.parseProp(prop); 
var options = $.setOption(this.elem,duration,easing,callback); 
var core = new fxCore(this.elem,props,options); 
return this; 
}, 
/** 
* 停止动画方法 
* 使用方法 catjs('your element id').stop(); 
*/ 
stop : function(){ 
$.removeData(this.elem); 
} 
} 
win.catfx = catfx; 
})(this,document);

使用起来也比较简单.直接catfx('ID').animate({'margin-left':200,'background-color':'#ff0000'},600,'easeOut',function(){});

跟jquery的使用方法差不多,如果不传第二个参数,则默认为400毫秒。不传第三个参数则默认匀速。第三个参数为函数,并且总共只有三个参数时。第三个参数为回调。

例:catfx('ID').animate({'margin-left':200,'background-color':'#ff0000'},600,function(){alert('洒家是回调函数~')});

Javascript 相关文章推荐
jquery实现的元素的left增加N像素 鼠标移开会慢慢的移动到原来的位置
Mar 21 Javascript
javascript之AJAX框架使用说明
Apr 24 Javascript
JS实现随机数生成算法示例代码
Aug 08 Javascript
js 数组去重的四种实用方法
Sep 09 Javascript
javascript表单验证和Window详解
Dec 11 Javascript
JavaScript中的this关键字使用详解
Aug 14 Javascript
Bootstrap使用基础教程详解
Sep 05 Javascript
js实现水平滚动菜单导航
Jul 21 Javascript
jQuery EasyUI window窗口使用实例代码
Dec 25 jQuery
详解在vue-test-utils中mock全局对象
Nov 07 Javascript
JS sort方法基于数组对象属性值排序
Jul 10 Javascript
vue 中的动态传参和query传参操作
Nov 09 Javascript
jquery方法+js一般方法+js面向对象方法实现拖拽效果
Aug 30 #Javascript
JS跨域代码片段
Aug 30 #Javascript
JS跨域总结
Aug 30 #Javascript
js中判断Object、Array、Function等引用类型对象是否相等
Aug 29 #Javascript
xml转json的js代码
Aug 28 #Javascript
基于jquery创建的一个图片、视频缓冲的效果样式插件
Aug 28 #Javascript
javascript 判断中文字符长度的函数代码
Aug 27 #Javascript
You might like
phpmyadmin 常用选项设置详解版
2010/03/07 PHP
CodeIgniter配置之config.php用法实例分析
2016/01/19 PHP
轻松掌握php设计模式之访问者模式
2016/09/23 PHP
Symfony2获取web目录绝对路径、相对路径、网址的方法
2016/11/14 PHP
PHP + plupload.js实现多图上传并显示进度条加删除实例代码
2017/03/06 PHP
详解laravel passport OAuth2.0的4种模式
2019/11/04 PHP
获取DOM对象的几种扩展及简写
2006/10/09 Javascript
JS图片切换的具体方法(带缩略图版)
2013/11/12 Javascript
jQuery插件jQuery-JSONP开发ajax调用使用注意事项
2013/11/22 Javascript
js中生成map对象的方法
2014/01/09 Javascript
用jquery等比例控制图片宽高的具体实现
2014/01/28 Javascript
avalonjs制作响应式瀑布流特效
2015/05/06 Javascript
新手快速学习JavaScript免费教程资源汇总
2015/06/25 Javascript
微信小程序 两种滑动方式(横向滑动,竖向滑动)详细及实例代码
2017/01/13 Javascript
如何写好你的JavaScript【推荐】
2017/03/02 Javascript
jQuery is not defined 错误原因与解决方法小结
2017/03/19 Javascript
如何在AngularJs中调用第三方插件库
2017/05/21 Javascript
js实现鼠标拖拽多选功能示例
2017/08/01 Javascript
js制作简单的音乐播放器的示例代码
2017/08/28 Javascript
说说node中的可读流和可写流的区别
2018/06/01 Javascript
老生常谈JS中的继承及实现代码
2018/07/06 Javascript
Vue刷新修改页面中数据的方法
2018/09/16 Javascript
Vue高版本中一些新特性的使用详解
2018/09/25 Javascript
ES6中的迭代器、Generator函数及Generator函数的异步操作方法
2019/05/12 Javascript
解决新建一个vue项目过程中遇到的问题
2020/10/22 Javascript
[03:49]显微镜下的DOTA2第十五期—VG登基之路完美团
2014/06/24 DOTA
解决pyPdf和pyPdf2在合并pdf时出现异常的问题
2020/04/03 Python
python查找特定名称文件并按序号、文件名分行打印输出的方法
2020/04/24 Python
Python接口自动化系列之unittest结合ddt的使用教程详解
2021/02/23 Python
CSS3动画效果回调处理详解
2014/12/10 HTML / CSS
Myprotein亚太地区:欧洲第一在线运动营养品牌
2020/12/20 全球购物
介绍下Lucene建立索引的过程
2016/03/02 面试题
给幼儿园老师的表扬信
2014/01/19 职场文书
医院护士见习期自我鉴定
2014/09/15 职场文书
2015年文员个人工作总结
2015/04/09 职场文书
红色经典观后感
2015/06/18 职场文书