留作备份,作为承诺,它已为框架实现对css3 transform2D的支持,尽管IE下对位移不那么完美。
它完美地实现了jquery动画模块95%的功能,而且天然支持颜色渐变,支持动画回放。
//=========================================// 动画模块v3//==========================================$.define("fx", "css",function(){ //$.log("已加载fx模块"); var types = { color:/color/i, transform:/rotate|scaleX|scaleY|translateX|translateY/i, scroll:/scroll/i, _default:/fontSize|fontWeight|opacity|width|height|top$|bottom$|left$|right$/i }, rfxnum = /^([+\-/\*]=)?([\d+.\-]+)([a-z%]*)$/i; var adapter = $.fxAdapter = { _default:{ get:function(el, prop) { return $.css(el,prop); }, tween :function(form,change,name,per) { var a = (form + change * $.easing[name](per)).toFixed(3); return isNaN(a) ? 0 : a; } }, type: function (attr){// 用于取得适配器的类型 for(var i in types){ if(types[i].test(attr)){ return i; } } return "_default"; } } var tween = adapter._default.tween; $.mix(adapter,{ scroll : { get: function(el, prop){ return el[prop]; }, tween: tween }, transform:{ get: function(el, prop){ return $.transform(el)[prop] }, set:function(el,t2d,isEnd,per){ var obj = {} for(var name in t2d){ obj[name] = isEnd ? t2d[name][1] : tween(t2d[name][0],t2d[name][2],t2d[name][3],per); } $.transform(el,obj); } }, color : { get:function(el,prop){ return $.css(el,prop); }, tween:function(f0,f1,f2,c0,c1,c2,name,per,i){ var delta = $.easing[name](per), ret = []; for(i = 0;i < 3;i++){ ret[i] = Math.max(Math.min((arguments[i] +arguments[i+3] * delta)|0, 255), 0); } return "rgb("+ret+")"; } } } ); //中央定时器,可以添加新节点到中央列队,然后通过setInterval方法不断调用nextTick处理所有节点的动画 function heartbeat( node) { heartbeat.nodes.push( node); if (heartbeat.id === null) { heartbeat.id = setInterval(nextTick, 13);//开始心跳 } return true; } heartbeat.nodes = []; //中央列队 heartbeat.id = null; //原始的setInterval id //驱动中央列队的元素节点执行它们的动画,如果执行完毕就把它们从列队中剔除,如果列队为空则中止心跳 function nextTick() { var nodes = heartbeat.nodes, i = 0, n = nodes.length; for (; i < n; i++) { if ( animate(nodes[i]) === false) {//在这里操作元素的样式或属性进行渐变 nodes.splice(i, 1); i -= 1; n -= 1; } } nodes.length || (clearInterval(heartbeat.id), heartbeat.id = null); } var keyworks = $.oneObject("before,frame,after,easing,revert,record"); //处理特效的入口函数,用于将第二个参数,拆分为两个对象props与config,然后再为每个匹配的元素指定一个双向列队对象linked //linked对象包含两个列队,每个列队装载着不同的特效对象 $.fn.fx = function( duration, hash ){ var props = hash ||{}, config = {}, p if(typeof duration === "function"){ props.after = duration; duration = null; } for( var name in props){ p = $.cssName(name) || name; if( name != p ){ props[ p ] = props[ name ]; delete props[ name ]; }else if( keyworks[name] ){ config[ name ] = props[ name ] delete props[ name ]; } } var easing = (config.easing || "swing").toLowerCase() ; config.easing = $.easing[ easing ] ? easing : "swing"; config.duration = duration || 500; config.method = "noop"; return this.each(function(node){ var linked = $._data(node,"fx") || $._data( node,"fx",{ positive: [], //正向列队 negative: [], //负向列队 run: false }); linked.positive.push({//fx对象 startTime: 0,//timestamp config: $.mix({}, config),//各种配置 props: $.mix({}, props)//用于渐变的属性 }); if(!linked.run){ linked.run = heartbeat( node); } }); } function animate( node ) {//linked对象包含两个列队(positive与negative) var linked = $._data( node,"fx") ; if(!linked){//如果在动画过程中,元素被移除掉 return false; } var fx = linked.positive[0], now, isEnd, mix; if( isFinite( fx ) ){//如果此时调用了delay方法,fx肯定是整型 setTimeout(function(){ linked.positive.shift(); linked.run = heartbeat( node); },fx) return (fx.run = false) } if (!fx) { //这里应该用正向列队的长度做判定 linked.run = false; } else { var config = fx.config, props = fx.props; if (fx.startTime) { // 如果已设置开始时间,说明动画已开始 now = +new Date; switch(linked.stopCode){//如果此时调用了stop方法 case 0: fx.render = $.noop;//中断当前动画,继续下一个动画 break; case 1: fx.gotoEnd = true;//立即跳到最后一帧,继续下一个动画 break; case 2: linked.positive = linked.negative = [];//清空该元素的所有动画 break; case 3: for(var ii=0, _fx; _fx= linked.positive[ii++]; ){ _fx.gotoEnd = true;//立即完成该元素的所有动画 } break; } delete linked.stopCode; isEnd = fx.gotoEnd || (now >= fx.startTime + config.duration); //node, 是否结束, 进度 fx.render(node, isEnd, (now - fx.startTime)/config.duration, $); // 处理渐变 if(fx.render === $.noop) { //立即开始下一个动画 linked.positive.shift(); }else{ if( (mix = config.frame ) && !isEnd ){ mix.call(node, node, props, fx ) ; } } if (isEnd) {//如果动画结束,则做还原,倒带,跳出列队等相关操作 if(config.method == "hide"){ for(var i in config.orig){//还原为初始状态 $.css( node, i, config.orig[i] ) } } linked.positive.shift(); //去掉播放完的动画 mix = config.after; mix && mix.call( node, node, fx.props, fx ) ; if (config.revert && linked.negative.length) { //开始倒带,将负向列队的动画加入播放列表中 [].unshift.apply(linked.positive, linked.negative.reverse()) linked.negative = []; // 清空负向列队 } if (!linked.positive.length) { linked.run = false; } } } else { // 初始化动画 fx.render = fxBuilder(node, linked, props, config); // 生成补间动画函数 mix = config.before mix && (mix.call( node, node, fx.props, fx ), config.before = 0); $[ config.method ].call(node, node, props, fx );//供show, hide 方法调用 fx.startTime = now = +new Date; } } return linked.run; // 调用 clearInterval方法,中止定时器 } function visible(node) { return $.css(node, "display") !== 'none'; } function fxBuilder( node, linked, props, config ){ var ret = "var style = node.style,t2d = {}, adapter = $.fxAdapter, _defaultTween = adapter._default.tween;", revertConfig = $.Object.merge( {}, config ), transfromChanged = 0, revertProps = {}; var orig = config.orig = {}, parts, to, from, val, unit, easing, op, type for(var name in props){ val = props[name] //取得结束值 if( val == null){ continue; } easing = config.easing;//公共缓动公式 type = $.fxAdapter.type(name); from = $.fxAdapter[ type ].get(node,name); //用于分解属性包中的样式或属性,变成可以计算的因子 if( val === "show" || (val === "toggle" && !visible(node))){ val = $._data(node,"old"+name) || from; config.method = "show"; from = 0; }else if(val === "hide" || val === "toggle" ){//hide orig[name] = $._data(node,"old"+name,from); config.method = "hide"; val = 0; }else if(typeof val === "object" && isFinite(val.length)){// array parts = val; val = parts[0];//取得第一个值 easing = parts[1] || easing;//取得第二个值或默认值 } //开始分解结束值to if(type != "color" ){//如果不是颜色,则需判定其有没有单位以及起止值单位不一致的情况 from = from == "auto" ? 0 : parseFloat(from)//确保from为数字 if( (parts = rfxnum.exec( val )) ){ to = parseFloat( parts[2] ),//确保to为数字 unit = $.cssNumber[ name ] ? "" : (parts[3] || "px"); if(parts[1]){ op = parts[1].charAt(0); if (unit && unit !== "px" && (op == "+" || op == "-") ) { $.css(node, name, (to || 1) + unit); from = ((to || 1) / parseFloat($.css(node,name))) * from; $.css( node, name, from + unit); } if(op){//处理+=,-= \= *= to = eval(from+op+to); } } var change = to - from; }else{ continue; } }else{ from = color2array(from); to = color2array(val); change = to.map(function(end,i){ return end - from[i] }); } if(from +"" === to +"" ){//不处理初止值都一样的样式与属性 continue; } var hash = { name: name, to: to, from: from , change: change, type: type, easing: easing, unit: unit }; switch( type ){ case "_default": if(name == "opacity" && !$.support.cssOpacity){ ret += $.format('$.css(node,"opacity", (isEnd ? #{to} : _defaultTween(#{from},#{change},"#{easing}", per )));;', hash); }else{ ret += $.format('style.#{name} = ((isEnd ? #{to} : _defaultTween(#{from}, #{change},"#{easing}",per )))+"#{unit}";', hash); } break; case "scroll": ret += $.format('node.#{name} = (isEnd ? #{to}: _defaultTween(#{from}, #{change},"#{easing}",per ));',hash); break; case "color": ret += $.format('style.#{name} = (isEnd ? "rgb(#{to})" : adapter.#{type}.tween(#{from}, #{change},"#{easing}",per));', hash); break; case "transform": transfromChanged++ ret += $.format('t2d.#{name} = [#{from},#{to}, #{change},"#{easing}"];',hash); break } if(type == "color"){ from = "rgb("+from.join(",")+")" } revertProps[ name ] = [ from , easing ]; } if( transfromChanged ){ ret += 'adapter.transform.set(node, t2d, isEnd, per);' } if ( config.record || config.revert ) { delete revertConfig.record; delete revertConfig.revert; linked.negative.push({ startTime: 0, reverting: 1,//标识正在倒带 config: revertConfig, props: revertProps }); } //生成补间函数 return Function( "node,isEnd,per,$",ret ); } $.easing = { linear: function( pos ) { return pos; }, swing: function( pos ) { return (-Math.cos(pos*Math.PI)/2) + 0.5; } } var colorMap = { "black":[0,0,0], "silver":[192,192,192], "gray":[128,128,128], "white":[255,255,255], "maroon":[128,0,0], "red":[255,0,0], "purple":[128,0,128], "fuchsia":[255,0,255], "green":[0,128,0], "lime":[0,255,0], "olive":[128,128,0], "yellow":[255,255,0], "navy":[0,0,128], "blue":[0,0,255], "teal":[0,128,128], "aqua":[0,255,255] }; var sandbox,sandboxDoc; function callSandbox(parent,callback){ if ( !sandbox ) { sandbox = document.createElement( "iframe" ); sandbox.frameBorder = sandbox.width = sandbox.height = 0; } parent.appendChild(sandbox); if ( !sandboxDoc || !sandbox.createElement ) { sandboxDoc = ( sandbox.contentWindow || sandbox.contentDocument ).document; sandboxDoc.write( ( document.compatMode === "CSS1Compat" ? "" : "" ) + "" ); sandboxDoc.close(); } callback(sandboxDoc); parent.removeChild(sandbox); } function parseColor(color) { var value; callSandbox( $.html, function(doc){ var range = doc.body.createTextRange(); doc.body.style.color = color; value = range.queryCommandValue("ForeColor"); }); return [value & 0xff, (value & 0xff00) >> 8, (value & 0xff0000) >> 16]; } function color2array(val) {//将字符串变成数组 var color = val.toLowerCase(),ret = []; if (colorMap[color]) { return colorMap[color]; } if (color.indexOf("rgb") == 0) { var match = color.match(/(\d+%?)/g), factor = match[0].indexOf("%") !== -1 ? 2.55 : 1 return (colorMap[color] = [ parseInt(match[0]) * factor , parseInt(match[1]) * factor, parseInt(match[2]) * factor ]); } else if (color.charAt(0) == '#') { if (color.length == 4) color = color.replace(/([^#])/g, '$1$1'); color.replace(/\w{2}/g,function(a){ ret.push( parseInt(a, 16)) }); return (colorMap[color] = ret); } if(window.VBArray){ return (colorMap[color] = parseColor(color)); } return colorMap.white; } var cacheDisplay = $.oneObject("a,abbr,b,span,strong,em,font,i,img,kbd","inline"); var blocks = $.oneObject("div,h1,h2,h3,h4,h5,h6,section,p","block"); $.mix(cacheDisplay ,blocks); function parseDisplay( nodeName ) { if ( !cacheDisplay[ nodeName ] ) { var body = document.body, elem = document.createElement(nodeName); body.appendChild(elem) var display = $.css( elem, "display" ); body.removeChild(elem); // 先尝试连结到当前DOM树去取,但如果此元素的默认样式被污染了,就使用iframe去取 if ( display === "none" || display === "" ) { callSandbox(body, function(doc){ elem = doc.createElement( nodeName ); doc.body.appendChild( elem ); display = $.css( elem, "display" ); }); } cacheDisplay[ nodeName ] = display; } return cacheDisplay[ nodeName ]; } //show 开始时计算其width1 height1 保存原来的width height display改为inline-block或block overflow处理 赋值(width1,height1) //hide 保存原来的width height 赋值为(0,0) overflow处理 结束时display改为none; //toggle 开始时判定其是否隐藏,使用再决定使用何种策略 $.mix( $, { fx: function ( nodes, duration, hash, effects ){ nodes = nodes.mass ? nodes : $(nodes); var props = hash || duration ; props = typeof props === "object" ? props : {} if(typeof duration === "function"){// fx(obj fn) hash = duration; // fx(obj, 500, fn) duration = 500; } if(typeof hash === "function"){ // fx(obj, num, fn) props.after = hash; // fx(obj, num, {after: fn}) } if(effects){ for(var i in effects){ if(typeof effects[i] === "function"){ var old = props[i]; props[i] = function(node, props, fx ){ effects[i].call(node, node, props, fx); if(typeof old === "function"){ old.call(node, node, props, fx); } } }else{ props[i] = effects[i] } } } return nodes.fx(duration, props); }, show: function(node, props){ if(node.nodeType == 1 && !visible(node)) { var old = $._data(node, "olddisplay"), _default = parseDisplay(node.nodeName), display = node.style.display = (old || _default); $._data(node, "olddisplay", display); // node.style.visibility = "visible"; if(props && ("width" in props || "height" in props)){//如果是缩放操作 //修正内联元素的display为inline-block,以让其可以进行width/height的动画渐变 if ( display === "inline" && $.css( node, "float" ) === "none" ) { if ( !$.support.inlineBlockNeedsLayout ) {//w3c node.style.display = "inline-block"; } else {//IE if ( _default === "inline" ) { node.style.display = "inline-block"; }else { node.style.display = "inline"; node.style.zoom = 1; } } } } } }, hide: function(node, props, fx){ if(node.nodeType == 1 && visible(node)){ var config = fx && fx.config; var display = $.css( node, "display" ); if ( display !== "none" && !$._data( node, "olddisplay" ) ) { $._data( node, "olddisplay", display ); } if( config ){//缩小 if("width" in props || "height" in props){//如果是缩放操作 //确保内容不会溢出,记录原来的overflow属性,因为IE在改变overflowX与overflowY时,overflow不会发生改变 config.overflow = [ node.style.overflow, node.style.overflowX, node.style.overflowY ]; node.style.overflow = "hidden"; } var after = config.after; config.after = function( node, fx, props ){ node.style.display = "none"; // node.style.visibility = "hidden"; if ( config.overflow != null && !$.support.keepSize ) { [ "", "X", "Y" ].forEach(function (postfix,index) { node.style[ "overflow" + postfix ] = config.overflow[index] }); } if(typeof after == "function"){ after.call( node, node, props, fx ); } }; }else{ node.style.display = "none"; } } }, toggle: function( node ){ $[ visible(node) ? "hide" : "show" ]( node ); } }); //如果clearQueue为true,是否清空列队 //如果jumpToEnd为true,是否跳到此动画最后一帧 $.fn.stop = function( clearQueue, jumpToEnd ){ clearQueue = clearQueue ? "1" : "" jumpToEnd = jumpToEnd ? "1" : "0" var stopCode = parseInt( clearQueue+jumpToEnd,2 );//返回0 1 2 3 return this.each(function(node){ var linked = $._data( node,"fx"); if(linked && linked.run){ linked.stopCode = stopCode; } }); } // 0 1 $.fn.delay = function(ms){ return this.each(function(node){ var linked = $._data(node,"fx") || $._data( node,"fx",{ positive:[], //正向列队 negative: [], //负向列队 run: false // }); linked.positive.push(ms); }); } var fxAttrs = [ [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], ["opacity"] ] function genFx( type, num ) {//生成属性包 var obj = {}; fxAttrs.concat.apply([], fxAttrs.slice(0,num)).forEach(function(name) { obj[ name ] = type; }); return obj; } var effects = { slideDown: genFx( "show", 1 ), slideUp: genFx( "hide", 1 ), slideToggle: genFx( "toggle", 1 ), fadeIn: { opacity: "show" }, fadeOut: { opacity: "hide" }, fadeToggle: { opacity: "toggle" } } Object.keys(effects).forEach(function( method ){ $.fn[ method ] = function( duration, hash ){ return $.fx( this, duration, hash, effects[method] ); } }); "show,hide".replace( $.rword, function( method ){ $.fn[ method ] = function(duration, hash){ if(!arguments.length){ return this.each(function(){ $[ method ]( this ); }) }else{ return $.fx( this, duration, hash, genFx( method , 3) ); } } }) var _toggle = $.fn.toggle; $.fn.toggle = function(duration,hash){ if(!arguments.length){ return this.each(function(node) { $.toggle( node ); }); }else if(typeof duration === "function" && typeof duration === "function" ){ return _toggle.apply(this,arguments) }else{ return $.fx(this, duration, hash, genFx("toggle", 3)); } } function beforePuff( node, props, fx ) { var position = $.css(node,"position"), width = $.css(node,"width"), height = $.css(node,"height"), left = $.css(node,"left"), top = $.css(node,"top"); node.style.position = "relative"; $.mix(props, { width: "*=1.5", height: "*=1.5", opacity: "hide", left: "-=" + parseInt(width) * 0.25, top: "-=" + parseInt(height) * 0.25 }); var after = fx.config.after; fx.config.after = function( node, props, fx ){ node.style.position = position; node.style.width = width; node.style.height = height; node.style.left = left; node.style.top = top; if(typeof after === "function"){ after.call( node, node, props, fx ); } } } //扩大1.5倍并淡去 $.fn.puff = function(duration, hash) { return $.fx( this, duration, hash, { before:beforePuff }); }});//2011.10.10 改进$.fn.stop//2011.10.20 改进所有特效函数,让传参更加灵活//2011.10.21 改进内部的normalizer函数//2012.2.19 normalizer暴露为$.fx 改进绑定回调的机制