本节案例将设计一个可折叠的面板,效果如图1所示。折叠面板默认显示为展开效果,当使用鼠标单击标题栏时,则折叠面板以动画形式逐步收起内容框,再次单击标题栏,内容框又会以动画形式缓慢展开。
展开效果
动画演示收起
图1 可折叠面板演示效果
这是一个简单的DOM界面应用效果,如果不考虑代码封装和优化,可以直接为标题栏绑定鼠标单击事件,然后在事件处理函数中通过条件语句设计内容框的显示或隐藏。
本节案例的目的不仅仅要完成这个折叠动画的任务,而是通过这个案例为大家提供一种更具通用的应用模式,解决UI动画设计的基本套路。案例借助闭包,把动画设计与管理封装在一个匿名函数内,并让匿名函数自执行,从而形成一个独立的、封闭的上下文环境,在该函数内声明一个全局类型函数animateManage(),并为该类型函数扩展大量原型方法,以便实现动画的高效管理。
- 设计闭包体
本例模式模仿jQuery结构。定义一个自执行匿名函数,并把全局对象window传入函数,然后在函数体内通过window.animateManage定义一个全局函数,并为该构造函数animateManage()定义原型对象。
(function (window, document, undefined) {
window.animateManage = function( optios ) { }
animateManage.prototype = {};
})(window, document)
实际上,上述写法完全可以转换为:
var animateManage = function( optios ) {}
animateManage.prototype = {};
(function (window, document, undefined) {})(window, document)
但是,本例写法能够实现在animateManage()构造函数或其原型对象中访问外部匿名函数的私有变量。由于函数animateManage()内部没有引用外部匿名函数的私有变量:
window.animateManage = function( optios ) {
this.context = optios;//当前对象 }
且使用new运算符调用它,创建实例化对象:
new animateManage({});
因此,外部匿名函数暂时还不是一个闭包体,调用完毕后自动被销毁。但是,animateManage()的原型对象包含很多方法,这些方法内部与外部匿名函数的私有变量保持联系。作为全局作用域上的对象,这些原型对象就构成了对外部匿名函数的引用,确保外部匿名函数被调用后,依然存在,从而形成一个持久存在的闭包体。
- 设计动画管理类
animateManage()是一个动画管理的类型函数,参数optios表示一个参数对象,可以设置如下属性:
- context:被操作的元素上下文。
- effect:动画效果的算法函数。
- time:效果的持续时间。
- starCss:元素的起始偏移量。
- css:元素的结束值偏移量。
animateManage()包含多个原型方法,具体说明和完整代码如下:
(function (window, document, undefined) {
var _aniQueue = [], //动画队列
_baseUID = 0, //元素的UID基础值
_aniUpdateTimer = 13, //动画更新的时间
_aniID = -1 , //检测的进程ID
isTicking = false; //检测状态
window.animateManage = function( optios ) {
this.context = optios; //当前对象
}
animateManage.prototype = {
init : function( ){ this.start(this.context);}, //初始化方法
stop : function(_e){ //停止动画
clearInterval(_aniID);
isTicking = false;
},
start : function(optios){ //开始动画
if(optios) this.pushQueue(optios); //填充队列属性
if(isTicking || _aniQueue.length === 0) return false;
this.tick();
return true;
},
tick : function(){ //动画检测
var self = this; isTicking = true;
_aniID = setInterval(function(){
if(_aniQueue.length === 0) {self.stop();}
else{
var i = 0, _aniLen = _aniQueue.length;
for(; i < _aniLen ; i++){
_aniQueue[i] && self.go(_aniQueue[i], i);
}
}
}, _aniUpdateTimer)
},
go : function(_options, i){ //执行具体的动画业务
var n = this.now(),
st = _options.startTime,
ting = _options.time,
e = _options.context,
t = st + ting,
name = _options.name,
tPos = _options.value,
sPos = _options.startValue,
effect = _options.effect,
scale = 1;
if(n >= t){//如果当前的时间 > 开始时间+结束时间则停止当前动画
_aniQueue[i] = null;
this.delQueue();
}else{
tPos = this.aniEffect({
e:e, ting :ting , n :n , st :st , sPos:sPos, tPos:tPos
},effect)
}
e.style[name] = name == "zIndex" ? tPos : tPos + "px";
this.goCallBack(_options.callback, _options.uid);//是否执行回调函数
},
aniEffect : function(_options, effect){ //动画效果,用户可以扩展动画算法
effect = effect || "linear";
var _effect ={
"linear":function(__options){ //线性运动
var scale = (__options.n - __options.st)/__options.ting,
tPos = __options.sPos + (__options.tPos - __options.sPos)*scale;
return tPos;
}
}
return _effect[effect](_options);
},
goCallBack : function(callback, u){ //回调
var i = 0,_aniLen = _aniQueue.length,isCallback = true;
for(; i < _aniLen ; i++){
if(_aniQueue[i].uid == u){isCallback = false;}
}
if(isCallback){callback && callback();}
},
pushQueue : function(options){ //压入执行动画队列
var con = options.context,
t = options.time || 1000,
callback = options.callback,
effect = options.effect,
starCss = options.starCss,
c = options.css, name = "",
u = this.setUID(con);
for(name in c){
_aniQueue.push({
"context" : con,
"time" : t,
"name" : name,
"value":parseInt(c[name], 10),
"startValue":parseInt((starCss[name] || 0)),
"effect":effect,
"uid" : u,
"callback":callback,
"startTime" : this.now()
})
}
},
delQueue : function(){ //删除动画队列中指定的动画
var i = 0, l = _aniQueue.length; //寻找到指定动画队列,将其删除
for(; i < l; i++ ){
if(_aniQueue[i] === null) _aniQueue.splice(i, 1);
}},
now : function(){ //获取现在时间
return new Date().getTime();},
getUID : function(_e){ //获取元素的UID
return _e.getAttribute("aniUID");},
setUID : function(_e, _v){ //设置元素的UID
var u = this.getUID(_e);
if(u) return u; //如果存在UID则直接返回
u = _v || _baseUID++; //生成UID
_e.setAttribute("aniUID", u);
return u;}
};
})(window, document)
- 应用动画管理类型
设计好动画管理的工具类型,就可以在具体案例中应用了。在本节示例中,为标题栏标签<dt id="header">绑定鼠标单击事件,在事件处理函数中,实例化animateManage()类型,并传入参数对象,然后调用init()方法,开始执行动画。完整代码可以参考本节实例源代码。
new animateManage({ //滑动展开广告
"context" : dd, //被操作的元素
"effect":"linear", //定义线性动画
"time": 1000, //持续时间
"starCss":{"height":300}, //元素的起始值偏移量
"css" :{"height":0} //元素的结束值偏移量
}).init();