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表格内容筛选实现思路及代码
Apr 16 Javascript
原生js三级联动的简单实现代码
Jun 07 Javascript
Bootstrap基本插件学习笔记之模态对话框(16)
Dec 08 Javascript
Flask中获取小程序Request数据的两种方法
May 12 Javascript
JS实现简单拖拽效果
Jun 21 Javascript
详解webpack2+React 实例demo
Sep 11 Javascript
JS实现简单获取最近7天和最近3天日期的方法
Apr 18 Javascript
微信小程序云开发之云函数详解
May 16 Javascript
JS实现横向轮播图(中级版)
Jan 18 Javascript
浅谈Vue3 Composition API如何替换Vue Mixins
Apr 29 Javascript
vue修改Element的el-table样式的4种方法
Sep 17 Javascript
Javascript执行上下文顺序的深入讲解
Nov 04 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_SELF的安全问题
2009/09/05 PHP
PHP parse_url 一个好用的函数
2009/10/03 PHP
php str_pad() 将字符串填充成指定长度的字符串
2010/02/23 PHP
php中unserialize返回false的解决方法
2014/09/22 PHP
php封装db类连接sqlite3数据库的方法实例
2017/12/19 PHP
Javascript日期对象的dateAdd与dateDiff方法
2008/11/18 Javascript
Javascript倒计时代码
2010/08/12 Javascript
js验证输入是否为手机号码或电话号码示例
2013/12/30 Javascript
jQuery Masonry瀑布流插件使用详解
2014/11/17 Javascript
jQuery判断对象是否存在的方法
2015/02/05 Javascript
JS动态修改iframe内嵌网页地址的方法
2015/04/01 Javascript
尝试动手制作javascript放大镜效果
2015/12/25 Javascript
javascript ASCII和Hex互转的实现方法
2016/12/27 Javascript
node.js express中app.param的用法详解
2017/07/16 Javascript
bootstrap时间控件daterangepicker使用方法及各种小bug修复
2017/10/25 Javascript
js 实现复选框只能选择一项的示例代码
2018/01/23 Javascript
vue数据操作之点击事件实现num加减功能示例
2019/01/19 Javascript
使用vue自定义指令开发表单验证插件validate.js
2019/05/23 Javascript
javascript canvas实现简易时钟例子
2020/09/05 Javascript
实例讲解Python设计模式编程之工厂方法模式的使用
2016/03/02 Python
Python使用matplotlib填充图形指定区域代码示例
2018/01/16 Python
numpy 计算两个数组重复程度的方法
2018/11/07 Python
python DataFrame 取差集实例
2019/01/30 Python
浅谈python3中input输入的使用
2019/08/02 Python
终于搞懂了Keras中multiloss的对应关系介绍
2020/06/22 Python
利用Python发送邮件或发带附件的邮件
2020/11/12 Python
CSS3 translate导致字体模糊的实例代码
2019/08/30 HTML / CSS
突袭HTML5之Javascript API扩展2—地理信息服务及地理位置API学习
2013/01/31 HTML / CSS
美国精品家居用品网站:US-Mattress
2016/08/24 全球购物
马来西亚网上美容店:Hermo.my
2017/11/25 全球购物
高中生毕业自我鉴定
2013/10/10 职场文书
文秘专业大学生求职信
2013/11/10 职场文书
高中数学教学反思
2014/01/30 职场文书
《兰亭集序》教学反思
2014/02/11 职场文书
销售经理岗位职责范本
2015/04/02 职场文书
面试通知短信
2015/04/20 职场文书