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中setTimeout的几种使用方法小结
Apr 07 Javascript
JavaScript中switch判断容易犯错的一个细节
Aug 27 Javascript
原生javascript获取元素样式
Dec 31 Javascript
jQuery实现带玻璃流光质感的手风琴特效
Nov 20 Javascript
基于JavaScript实现动态添加删除表格的行
Feb 01 Javascript
Three.js学习之Lamber材质和Phong材质
Aug 04 Javascript
jQuery实现用户信息表格的添加和删除功能
Sep 12 jQuery
vue 使用eventBus实现同级组件的通讯
Mar 02 Javascript
详解React-Router中Url参数改变页面不刷新的解决办法
May 08 Javascript
Vue-cli3项目配置Vue.config.js实战记录
Jul 29 Javascript
对vue下点击事件传参和不传参的区别详解
Sep 15 Javascript
如何在vue 中使用柱状图 并自修改配置
Jan 21 Vue.js
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实现异步将远程链接上内容(图片或内容)写到本地的方法
2016/11/30 PHP
PHP+Mysql+Ajax实现淘宝客服或阿里旺旺聊天功能(前台页面)
2017/06/16 PHP
php大小写转换函数(strtolower、strtoupper)用法介绍
2017/11/17 PHP
thinkPHP3.2.3实现阿里大于短信验证的方法
2018/06/06 PHP
Javascript学习笔记二 之 变量
2010/12/15 Javascript
javascript 内存回收机制理解
2011/01/17 Javascript
JS对外部文件的加载及对IFRMAME的加载的实现,当加载完成后,指定指向方法(方法回调)
2011/07/04 Javascript
caller和callee的区别介绍及演示结果
2013/03/10 Javascript
js数组的基本用法及数组根据下标(数值或字符)移除元素
2013/10/20 Javascript
jquery中get和post的简单实例
2014/02/04 Javascript
原生js事件的添加和删除的封装
2014/07/01 Javascript
javascript上下方向键控制表格行选中并高亮显示的方法
2015/02/13 Javascript
轻量级的原生js日历插件calendar.js使用指南
2015/04/28 Javascript
pc加载更多功能和移动端下拉刷新加载数据
2016/11/07 Javascript
SelecT下拉框选中和取值的解决方法
2016/11/22 Javascript
利用JS实现简单的日期选择插件
2017/01/23 Javascript
node安装--linux下的快速安装教程
2017/03/21 Javascript
javascript定时器取消定时器及优化方法
2017/07/08 Javascript
Vue2路由动画效果的实现代码
2017/07/10 Javascript
在Vue-cli里应用Vuex的state和mutations方法
2018/09/16 Javascript
Vue框架里使用Swiper的方法示例
2018/09/20 Javascript
js通过canvas生成图片缩略图
2020/10/02 Javascript
Python函数中*args和**kwargs来传递变长参数的用法
2016/01/26 Python
详解使用 pyenv 管理多个版本 python 环境
2017/10/19 Python
PyQt5每天必学之进度条效果
2018/04/19 Python
python实现决策树分类
2018/08/30 Python
python代理工具mitmproxy使用指南
2019/07/04 Python
python web框架 django wsgi原理解析
2019/08/20 Python
python3 logging日志封装实例
2020/04/08 Python
德国网上超市:myTime.de
2019/08/26 全球购物
小学美术教学反思
2014/02/01 职场文书
赵乐秦在党的群众路线教育实践活动总结大会上的讲话稿
2014/10/25 职场文书
2014年专项整治工作总结
2014/11/17 职场文书
满月酒邀请函
2015/01/30 职场文书
外贸采购员岗位职责
2015/04/03 职场文书
maven依赖的version声明控制方式
2022/01/18 Java/Android