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 相关文章推荐
JavaScript与C# Windows应用程序交互方法
Jun 29 Javascript
javascript事件模型代码
Jul 01 Javascript
jquery 获取json数据实现代码
Apr 27 Javascript
JavaScript:Div层拖动效果实例代码
Aug 06 Javascript
js post提交调用方法
Feb 12 Javascript
JavaScript实现定时隐藏与显示图片的方法
Aug 06 Javascript
Angular.Js之Scope作用域的学习教程
Apr 27 Javascript
了解JavaScript中的选择器
May 24 Javascript
Vue项目总结之webpack常规打包优化方案
Jun 06 Javascript
利用JS响应式修改vue实现页面的input值
Sep 02 Javascript
在vue中把含有html标签转为html渲染页面的实例
Oct 28 Javascript
VSCode搭建React Native环境
May 07 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
php模拟服务器实现autoindex效果的方法
2015/03/10 PHP
简单三步,搞掂内存泄漏
2007/03/10 Javascript
jquery load()在firefox(火狐)下显示不正常的解决方法
2011/04/05 Javascript
artDialog 4.1.5 Dreamweaver代码提示/补全插件 附下载
2012/07/31 Javascript
js select option对象小结
2013/12/20 Javascript
动态加载js、css等文件跨iframe实现
2014/02/24 Javascript
百度地图API之本地搜索与范围搜索
2015/07/30 Javascript
全面解析Bootstrap中transition、affix的使用方法
2016/05/30 Javascript
js简单正则验证汉字英文及下划线的方法
2016/11/28 Javascript
使用ES6语法重构React代码详解
2017/05/09 Javascript
JS 组件系列之BootstrapTable的treegrid功能
2017/06/16 Javascript
node.js中路由,中间件,ge请求和post请求的参数详解
2017/12/26 Javascript
js+canvas实现验证码功能
2020/09/21 Javascript
浅谈VUE单页应用首屏加载速度优化方案
2018/08/28 Javascript
使用express来代理服务的方法
2019/06/21 Javascript
实现一个 Vue 吸顶锚点组件方法
2019/07/10 Javascript
js实现中文实时时钟
2020/01/15 Javascript
JS数组方法reduce的用法实例分析
2020/03/03 Javascript
jQuery+ThinkPHP实现图片上传
2020/07/23 jQuery
Python将xml和xsl转换为html的方法
2015/03/10 Python
使用Python的内建模块collections的教程
2015/04/28 Python
Python操作Word批量生成文章的方法
2015/07/28 Python
Python+matplotlib+numpy绘制精美的条形统计图
2018/01/02 Python
Python设计模式之中介模式简单示例
2018/01/09 Python
儿童python练习实例
2018/05/27 Python
python 使用正则表达式按照多个空格分割字符的实例
2018/12/20 Python
Python多叉树的构造及取出节点数据(treelib)的方法
2019/08/09 Python
使用python快速实现不同机器间文件夹共享方式
2019/12/22 Python
Python接口自动化测试框架运行原理及流程
2020/11/30 Python
阿迪达斯荷兰官方网站:adidas荷兰
2018/03/16 全球购物
Linux的主要特性
2016/09/03 面试题
毕业生就业推荐表自我鉴定
2014/03/20 职场文书
环保标语大全
2014/06/12 职场文书
国家奖学金获奖感言
2014/08/16 职场文书
教你用eclipse连接mysql数据库
2021/04/22 MySQL
通过Qt连接OpenGauss数据库的详细教程
2021/06/23 PostgreSQL