mass Framework fx模块 v4
mass Framework第四代动画引擎,支持CSS3的变形动画,全新的中央列队,与jQuery API完全兼容。
//=========================================
// 动画模块v4
//==========================================
define("fx", ["$css"],function(){
var types = {
color:/color/i,
scroll:/scroll/i,
transform: /transform/i
},
rfxnum = /^([+\-/*]=)?([\d+.\-]+)([a-z%]*)$/i;
function isHidden( elem ) {
return elem.sourceIndex === 0 || $.css( elem, "display" ) === "none" || !$.contains( elem.ownerDocument.documentElement, elem );
}
$.mix({//缓动公式
easing : {
linear: function( pos ) {
return pos;
},
swing: function( pos ) {
return (-Math.cos(pos*Math.PI)/2) + 0.5;
}
},
"@queue": [],//主列队
tick: function(fx){//用于向主列队或元素的子列队插入动画实例,并会让停走了的定时器再次动起来
var gotoQueue = true;
for(var i = 0, el; el = $["@queue"][i++];){
if(el.symbol == fx.symbol){//★★★第一步
el.positive.push(fx);//子列队
gotoQueue = false
break;
}
}
if(gotoQueue){//★★★第二步
fx.positive = fx.positive || [];
$["@queue"].unshift( fx );
}
if ($.tick.id === null) {
$.tick.id = setInterval( nextTick, 1000/ fx.fps );//原始的setInterval id并执行动画
}
},
//由于构建更高级的基于元素节点的复合动画
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, fx ){
effects[i].call(node, node, fx);
if(typeof old === "function"){
old.call(node, node, fx);
}
}
}else{
props[i] = effects[i]
}
}
}
return nodes.fx(duration || 500, props);
},
//show 开始时计算其width1 height1 保存原来的width height display改为inline-block或block overflow处理 赋值(width1,height1)
//hide 保存原来的width height 赋值为(0,0) overflow处理 结束时display改为none;
//toggle 开始时判定其是否隐藏,使用再决定使用何种策略
show: function(node, fx){
if(node.nodeType == 1 && isHidden(node)) {
var display = $._data(node, "olddisplay");
if(!display || display == "none"){
display = parseDisplay(node.nodeName)
$._data(node, "olddisplay", display);
}
node.style.display = display;
if(fx && ("width" in fx || "height" in fx)){//如果是缩放操作
//修正内联元素的display为inline-block,以让其可以进行width/height的动画渐变
if ( display === "inline" && $.css( node, "float" ) === "none" ) {
if ( !$.support.inlineBlockNeedsLayout ) {//w3c
node.style.display = "inline-block";
} else {//IE
if ( display === "inline" ) {
node.style.display = "inline-block";
}else {
node.style.display = "inline";
node.style.zoom = 1;
}
}
}
}
}
},
hide: function(node, fx){
if(node.nodeType == 1 && !isHidden(node)){
var display = $.css( node, "display" );
if ( display !== "none" && !$._data( node, "olddisplay" ) ) {
$._data( node, "olddisplay", display );
}
if( fx ){//缩小
if("width" in fx || "height" in fx){//如果是缩放操作
//确保内容不会溢出,记录原来的overflow属性,因为IE在改变overflowX与overflowY时,overflow不会发生改变
fx.overflow = [ node.style.overflow, node.style.overflowX, node.style.overflowY ];
node.style.overflow = "hidden";
}
var after = fx.after;
fx.after = function( node, fx ){
node.style.display = "none";
if ( fx.overflow != null && !$.support.keepSize ) {
[ "", "X", "Y" ].forEach(function (postfix,index) {
node.style[ "overflow" + postfix ] = fx.overflow[index]
});
}
if(typeof after == "function"){
after.call( node, node, fx );
}
};
}else{
node.style.display = "none";
}
}
},
toggle: function( node ){
$[ isHidden(node) ? "show" : "hide" ]( node );
}
})
//用于从主列队中剔除已经完成或被强制完成的动画实例,一旦主列队被清空,还负责中止定时器,节省内存
function nextTick() {
var fxs = $["@queue"], i = fxs.length;
while(--i >= 0){
if ( !(fxs[i].symbol && animate(fxs[i], i)) ) {
fxs.splice(i, 1);
}
}
fxs.length || (clearInterval($.tick.id), $.tick.id = null);
}
$.tick.id = null;
$.fn.fx = function( duration, hash, /*internal*/ p ){
if(typeof duration === "number" ){
hash = hash || {};
for( var name in hash){
p = $.cssName(name) || name;
if( name != p ){
hash[ p ] = hash[ name ];//收集用于渐变的属性
delete hash[ name ];
}
}
if(typeof hash.easing !== "function"){//转换easing属性为缓动公式
var easing = (hash.easing || "swing").toLowerCase() ;
hash.easing = $.easing[ easing ] || $.easing.swing;
}
for(var i = 0, node; node = this[i++];){
var fx = new Fx;
$.mix(fx, hash)
fx.method = "noop"
fx.duration = duration
fx.symbol = node;
$.tick( fx );
}
return this;
}else{
throw "First argument must be number "
}
}
var cssTransform = $.support.transform
$.mix($.fx, {
fps: 30,
"@debug": 1,
type: function (attr){// 用于取得适配器的类型
for(var i in types){
if(types[i].test(attr)){
return i;
}
}
return "_default";
},
update: {
scroll: function(node, per, end, obj){
node[obj.name] = (end ? obj.to : obj.from + (obj.to - obj.from ) * obj.easing(per) ) + obj.unit
},
color: function(node, per, end, obj){
var pos = obj.easing( per ),
rgb = end ? obj.to : obj.from.map(function(from, i){
return Math.max(Math.min( parseInt( from + (obj.to[i] - from) * pos, 10), 255), 0);
});
node.style[obj.name] = "rgb(" + rgb + ")";
},
transform: function(node, per, end, obj){
if(!obj.parsed){
var t = new $.Matrix2D
t.set.apply(t, obj.from)
obj.from = t.decompose();
t.set.apply(t, obj.to)
obj.to = t.decompose();
obj.parsed = 1;
}
var pos = obj.easing(per), transform = "", unit, startVal, endVal, i = obj.from.length;
while ( i-- ) {
startVal = obj.from[i];
endVal = obj.to[i];
unit = +false;
switch ( startVal[0] ) {
case "translate":
unit = "px";
case "scale":
unit || ( unit = "");
transform = startVal[0] + "(" +
(end ? endVal[1][0]: (startVal[1][0] + (endVal[1][0] - startVal[1][0]) * pos).toFixed(7) ) + unit +","+
(end ? endVal[1][1]: (startVal[1][1] + (endVal[1][1] - startVal[1][1]) * pos).toFixed(7) ) + unit + ") "+
transform;
break;
case "skewX":
case "rotate":
transform = startVal[0] + "(" +
(end ? endVal[1]: (startVal[1] + (endVal[1] - startVal[1]) * pos).toFixed(7) ) +"rad) "+
transform;
break;
}
}
if(cssTransform){
node.style[ obj.name ] = transform;
}else{
$(node).css("transform",transform );
}
}
},
parse: {
color:function(node, from, to){
return [ color2array(from), color2array(to) ]
},
transform: function(node, from, to){
var zero = "matrix(1,0,0,1,0,0)"
from = from == "none" ? zero : from;
if(to.indexOf("matrix") == -1 ){
var neo = node.cloneNode(true);
//webkit与opera如果display为none,无法取得其变形属性
neo.style.position = "relative";
neo.style.opacity = "0";
node.parentNode.appendChild(neo)
neo = $(neo).css("transform", to);
to = neo.css("transform");
neo.remove();
}
to = (from +" "+ to).match(/[-+.e\d]+/g).map(function(el){
return el * 1
});
from = to.splice(0,6);
return [from, to]
}
},
_default: $.css,
scroll: function(el, prop){
return el[ prop ];
}
});
if(window.WebKitCSSMatrix){
$.fx.parse.transform = function(node, from, to){
var first = new WebKitCSSMatrix(from), second = new WebKitCSSMatrix(to)
from = [], to = [];
"a,b,c,d,e,f".replace($.rword, function(p){
from.push( first[ p ] )
to.push( second[ p ] )
});
return [from, to]
}
}
if(!$.support.cssOpacity){
$.fx.update.opacity = function(node, per, end, obj){
$.css(node,"opacity", (end ? obj.to : obj.from + obj.easing(per) * (obj.to - obj.from) ))
}
types.opacity = /opacity/i;
}
var Fx = function(){}
Fx.prototype.update = function(per, end){
var node = this.symbol;
for(var i = 0, obj; obj = this.props[i++];){
var fn = $.fx.update[obj.type]
if(fn){
fn(node, per, end, obj)
}else{
node.style[obj.name] = (end ? obj.to : obj.from + obj.easing(per) * (obj.to - obj.from) ) + obj.unit
}
}
}
var keyworks = $.oneObject("orig,overflow,before,frame,after,easing,revert,record");
//用于生成动画实例的关键帧(第一帧与最后一帧)所需要的计算数值与单位,并将回放用的动画放到negative子列队中去
function fxBuilder(node, fx, index ){
var to, parts, unit, op, props = [], revertProps = [],orig = {}, hidden = isHidden(node);
for(var name in fx){
if(!fx.hasOwnProperty(name) && keyworks[name]){
continue
}
var val = fx[name] //取得结束值
var easing = fx.easing;//公共缓动公式
var type = $.fx.type(name);
var from = ($.fx[ type ] || $.fx._default)(node, name);//取得起始值
//用于分解属性包中的样式或属性,变成可以计算的因子
if( val === "show" || (val === "toggle" && hidden)){
val = $._data(node,"old"+name) || from;
fx.method = "show";
from = 0;
$.css(node, name, 0 );
}else if(val === "hide" || val === "toggle" ){//hide
orig[name] = $._data(node,"old"+name, from );
fx.method = "hide";
val = 0;
}else if($.isArray( val )){// array
parts = val;
val = parts[0];//取得第一个值
easing = typeof parts[1] == "function" ? parts[1]: easing;//取得第二个值或默认值
}
if($.fx.parse[ type ]){
parts = $.fx.parse[ type ](node, from, val );
}else{
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);
}
}
parts = [from, to]
}else{
parts = [0, 0]
}
}
from = parts[0];
to = parts[1];
if( from +"" === to +"" ){//不处理初止值都一样的样式与属性
continue
}
var prop = {
name: name,
from: from ,
to: to,
type: type,
easing: easing,
unit: unit
}
props.push( prop );
revertProps.push($.mix({}, prop,{
to: from,
from: to
}))
}
fx.props = props;
fx.orig = orig;
if ( fx.record || fx.revert ) {
var fx2 = new Fx;//回滚到最初状态
for( name in fx ){
fx2[ name ] = fx[ name ];
}
fx2.record = fx2.revert = void 0
fx2.props = revertProps;
var el = $["@queue"][ index ];
el.negative = el.negative || [];
el.negative.push(fx2);//添加已存负向列队中
}
}
//驱动主列队的动画实例进行补间动画(update),执行各种回调(before, frame, after),
//并在动画结束后,从子列队选取下一个动画实例取替自身
function animate( fx, index ) {
var node = fx.symbol, now = +new Date, mix;
if(!fx.startTime){//第一帧
mix = fx.before;//位于动画的最前面
mix && ( mix.call( node, node, fx ), fx.before = 0 );
if(!fx.props){//from这个值必须在此个时间点才能侦察正确
fxBuilder( fx.symbol, fx, index ); //添加props属性与设置负向列队
}
$[ fx.method ].call(node, node, fx );//这里用于设置node.style.display
fx.startTime = now;
}else{
var per = (now - fx.startTime) / fx.duration;
var end = fx.gotoEnd || per >= 1;
fx.update( per, end ); // 处理渐变
if( (mix = fx.frame ) && !end ){
mix.call(node, node, fx ) ;
}
if ( end ) {//最后一帧
if(fx.method == "hide"){
for(var i in fx.orig){//还原为初始状态
$.css( node, i, fx.orig[i] );
}
}
mix = fx.after;//执行动画完成后的回调
mix && mix.call( node, node, fx ) ;
if( fx.revert && fx.negative.length){
Array.prototype.unshift.apply( fx.positive, fx.negative.reverse());
fx.negative = []; // 清空负向列队
}
var neo = fx.positive.shift();
if ( !neo ) {
return false;
}
$["@queue"][ index ] = neo;
neo.positive = fx.positive;
neo.negative = fx.negative;
}
}
return true;
}
$.fn.delay = function( ms ){
return this.fx( ms );
}
//如果clearQueue为true,是否清空列队
//如果gotoEnd 为true,是否跳到此动画最后一帧
$.fn.stop = function( clearQueue, gotoEnd ){
clearQueue = clearQueue ? "1" : ""
gotoEnd = gotoEnd ? "1" : "0"
var stopCode = parseInt( clearQueue+gotoEnd ,2 );//返回0 1 2 3
var array = $["@queue"];
return this.each(function(node){
for(var i = 0, fx ; fx = array[i];i++){
if(fx.symbol === node){
switch(stopCode){//如果此时调用了stop方法
case 0: //中断当前动画,继续下一个动画
fx.update = fx.after = fx.frame = $.noop
fx.revert && fx.negative.shift();
fx.gotoEnd = true;
break;
case 1://立即跳到最后一帧,继续下一个动画
fx.gotoEnd = true;
break;
case 2://清空该元素的所有动画
delete fx.symbol
break;
case 3:
Array.prototype.unshift.apply( fx.positive,fx.negative.reverse());
fx.negative = []; // 清空负向列队
for(var j =0; fx = fx.positive[j++]; ){
fx.before = fx.after = fx.frame = $.noop
fx.gotoEnd = true;//立即完成该元素的所有动画
}
break;
}
}
}
});
}
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;
if(~name.indexOf("margin")){
$.fx.update[name] = function(node, per, end, obj){
var val = (end ? obj.to : obj.from + ( obj.from - obj.to) * obj.easing(per) ) ;
node.style[name] = Math.max(val,0) + obj.unit;
}
}
});
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] );
}
});
[ "toggle", "show", "hide" ].forEach(function( name, i ) {
var toggle = $.fn[ name ];
$.fn[ name ] = function( duration, hash ) {
if(!arguments.length ){
return this.each(function(node) {
$.toggle( node );
});
}else if(!i && typeof duration === "function" && typeof duration === "function" ){
return toggle.apply(this,arguments)
}else{
return $.fx( this, duration, hash, genFx( name , 3) );
}
};
});
function beforePuff( node, 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(fx, {
width: "*=1.5",
height: "*=1.5",
opacity: "hide",
left: "-=" + parseInt(width) * 0.25,
top: "-=" + parseInt(height) * 0.25
});
var after = fx.after;
fx.after = function( node, 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, fx );
}
}
}
//扩大1.5倍并淡去
$.fn.puff = function(duration, hash) {
return $.fx( this, duration, hash, {
before: beforePuff
});
}
var colorMap = {
"black":[0,0,0],
"gray":[128,128,128],
"white":[255,255,255],
"orange":[255, 165, 0],
"red":[255,0,0],
"green":[0,128,0],
"yellow":[255,255,0],
"blue":[0,0,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( ( $.support.boxModel ? "<!doctype html>" : "" ) + "<html><body>" );
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;
}
$.parseColor = color2array
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 ];
}
})
/**
2011.10.10 改进$.fn.stop
2011.10.20 改进所有特效函数,让传参更加灵活
2011.10.21 改进内部的normalizer函数
2012.2.19 normalizer暴露为$.fx 改进绑定回调的机制
2012.5.17 升级到 v4
2012.5.19 $.fx.parse.transform FIX BUG
2012.6.1 优化show hide toggle方法
机器瞎学/数据掩埋/模式混淆/人工智障/深度遗忘/神经掉线/计算机幻觉/专注单身二十五年
浙公网安备 33010602011771号