mass Framework fx模块 v3
留作备份,作为承诺,它已为框架实现对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 改进绑定回调的机制 |
如果您觉得此文有帮助,可以打赏点钱给我支付宝1669866773@qq.com ,或扫描二维码


机器瞎学/数据掩埋/模式混淆/人工智障/深度遗忘/神经掉线/计算机幻觉/专注单身二十五年
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?