本节案例将设计一个可折叠的面板,效果如图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();