jQuery-1.9.1源码分析系列(七) 钩子(hooks)机制及浏览器兼容续

  前面一章分析了jQuery.support、钩子原理和属性钩子。这一章主要分析CSS钩子。

 

b. css操作的钩子


  CSS钩子种类:

  cssHooks

  cssNumber

  cssProps

 

jQuery.cssHooks的对象

  不过cssHooks中的set函数的作用有些不同,set函数并没有真正的设置相应的值,而是修正要设置到CSS中的值。获取到修正值以后,设置在jQuery.style函数中进行。后面分析几个CSS钩子

  获取opacity返回的值需要时数字

cssHooks: {
  opacity: {
    get: function( elem, computed ) {
      if ( computed ) {
        //需要返回数字                      
        var ret = curCSS( elem, "opacity" );
        return ret === "" ? "1" : ret;
      }
    }
  }
},
View Code

  当设置display为none等的时候是不能获取到宽高的,所以需要将元素设为display为block,visibility设置为hidden来获取宽高;设置宽高需要根据CSS样式boxSizing的取值来确定。

jQuery.each([ "height", "width" ], function( i, name ) {
  jQuery.cssHooks[ name ] = {
    get: function( elem, computed, extra ) {
      if ( computed ) {
               //当设置display为none等的时候是不能获取到宽高的,
                //所以需要将元素设为display为block,
               //visibility设置为hidden来获取宽高
               // rdisplayswap = /^(none|table(?!-c[ea]).+)/,
               //cssShow ={ position: "absolute", visibility: "hidden", display: "block" }
 
               return elem.offsetWidth === 0 && rdisplayswap.test( jQuery.css( elem, "display" ) ) ?
                jQuery.swap( elem, cssShow, function() {
                    return getWidthOrHeight( elem, name, extra );

                }) :
                getWidthOrHeight( elem, name, extra );
           }
       },
       
       set: function( elem, value, extra ) {
      var styles = extra && getStyles( elem );
      return setPositiveNumber( elem, value, extra ?
        augmentWidthOrHeight(elem, name, extra,
          jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box",styles) : 
        0
      );
    }
  };
});
View Code

 

  IE使用filters来设置不透明度;IE要设置不透明度更加复杂,需保证有布局,如果设置不透明度为1,并且没有别的filters存在,尝试移除filter属性等

if ( !jQuery.support.opacity ) {
  jQuery.cssHooks.opacity = {
    get: function( elem, computed ) {
      // IE使用filters来设置不透明度,ropacity = /opacity\s*=\s*([^)]*)/
      return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
      ( 0.01 * parseFloat( RegExp.$1 ) ) + "" :
      computed ? "1" : "";
    },
    set: function( elem, value ) {
      var style = elem.style,
      currentStyle = elem.currentStyle,
      opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
      filter = currentStyle && currentStyle.filter || style.filter || "";
 
      //IE7中滤镜(filter)必须获得布局才能生效,我们用zoom这个IE私有属性让其获得布局
      style.zoom = 1;
 
      //ie处理,如果设置不透明度为1,并且没有别的filters存在,尝试移除filter属性
      //如果只为“”,删除内联的opacity,ralpha = /alpha\([^)]*\)/i
      if ( ( value >= 1 || value === "" ) && jQuery.trim( filter.replace( ralpha, "" ) ) === "" && style.removeAttribute ) {
        //设置style.filter为null, "" 或 " ",结果是"filter:"依然在cssText中
        //如果当下"filter:"存在则清除类型不可用,我们应当避免
        // style.removeAttribute是IE独有
        style.removeAttribute( "filter" );
 
        //如果当前没有filter样式应用于css rule或未设置内联的不透明则返回
        if ( value === "" || currentStyle && !currentStyle.filter ) {
          return;
        }
      }
 
      // 其他情况设置filter values
      style.filter = ralpha.test( filter ) ?
      filter.replace( ralpha, opacity ) :
      filter + " " + opacity;
    }
  };
}
View Code

  webkit的bug:getComputedStyle返回margin-right值错误;当指定为top/left/bottom/right时,使用getComputedStyle 返回百分比结果,使用jQuery( elem ).position()来获取。

//DOM加载完成后才能做support测试,在添加下面的Hooks
jQuery(function() {
    if ( !jQuery.support.reliableMarginRight ) {
           jQuery.cssHooks.marginRight = {
               get: function( elem, computed ) {
                   if ( computed ) {
                       // WebKit Bug 13343 - getComputedStyle返回margin-right值错误
                       //设置元素的display为inline-block来解决
                       return jQuery.swap( elem, { "display": "inline-block" },
                               curCSS, [ elem, "marginRight" ] );
                   }
               }
           };
    }
 
    // Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
    // getComputedStyle 返回百分比当指定为top/left/bottom/right
    //我们使用jQuery( elem ).position()来获取
    if ( !jQuery.support.pixelPosition && jQuery.fn.position ) {
           jQuery.each( [ "top", "left" ], function( i, prop ) {
               jQuery.cssHooks[ prop ] = {
                    get: function( elem, computed ) {
                       if ( computed ) {
                               computed = curCSS( elem, prop );
                               // if curCSS returns percentage, fallback to offset
                               //rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ),
                               return rnumnonpx.test( computed ) ?
                               jQuery( elem ).position()[ prop ] + "px" :
                               computed;
                       }
                   }
               };
           });
    }
});
View Code

  后面这个钩子与众不同,他使用在动画的展开上。我们知道margin/padding/borderWidth实际上不是一个CSS属性,而是四个CSS属性的集合。所以三个CSS的expand钩子实际上是将他们拆分成四个属性给提取出来。

  // 这里的hooks用在动画的展开特征上
  jQuery.each({
        margin: "",
        padding: "",
        border: "Width"
        }, function( prefix, suffix ) {
            jQuery.cssHooks[ prefix + suffix ] = {
                expand: function( value ) {
                    var i = 0,
                    expanded = {},

                        //如果不是字符串则假设为一个单独的数字
                        parts = typeof value === "string" ? value.split(" ") : [ value ];

                        for ( ; i < 4; i++ ) {
                            //cssExpand = [ "Top", "Right", "Bottom", "Left" ],
                            expanded[ prefix + cssExpand[ i ] + suffix ] =
                            parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
                        }

                        return expanded;
                    }
            };

            if ( !rmargin.test( prefix ) ) {
            jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
        }
    });
View Code

 

jQuery. cssNumber和jQuery.cssProps的对象

//下面的css特征值后面不能添加“px”字段
cssNumber: {
    "columnCount": true,
    "fillOpacity": true,
    "fontWeight": true,
    "lineHeight": true,
    "opacity": true,
    "orphans": true,
    "widows": true,
    "zIndex": true,
    "zoom": true
},
 
//float对应的css特征名需要在使用前修正
cssProps: {
    // normalize float css property
    "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
},

  拓展:

  有一些CSS属性需要在某些特定条件下才能获取正确。这种情况需要模拟场景获取值,然后恢复先前的场景。swap就是用来专门模拟场景,获取值以后恢复场景的函数。

// 一种快速切换输入/输出css特征值(计算前保存css特征,计算中更改css特征以获取计算结果,计算结束后恢复先前保存的css特征)以获取正确计算结果的方法
swap: function( elem, options, callback, args ) {
    var ret, name,
        old = {};
 
    // 保存原来的特征值,设置为保证计算成功而修改的特征(property)值
    for ( name in options ) {
           old[ name ] = elem.style[ name ];
           elem.style[ name ] = options[ name ];
    }
    //调用回调计算结果
    ret = callback.apply( elem, args || [] );
 
    // 恢复原来的特征值
    for ( name in options ) {
           elem.style[ name ] = old[ name ];
    }
    //返回计算结果
    return ret;
}

  setPositiveNumber函数对要设置给CSS属性的值做修正,比如添加"px"结尾等

// rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ),
//用来匹配数字,.source返回表达式字符串自身
//core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,
//返回指定value和subtract对应应该设置的css样式值
function setPositiveNumber( elem, value, subtract ) {
    var matches = rnumsplit.exec( value );
    return matches ?
    // 注意没有定义的"subtract",例如在cssHooks中使用时
    Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
    value;
}

  getWidthOrHeight函数提供获取CSS宽高属性的计算方法

//获取宽度或高度
function getWidthOrHeight( elem, name, extra ) {
    //首先获取offset特征值,相当于包括边框在内的盒宽高
    var valueIsBorderBox = true,
        val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
        styles = getStyles( elem ),
        isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box";

    //一些非html元素offsetWidth返回undefined,因此检查null/undefined
    if ( val <= 0 || val == null ) {
        //如果计算失败则在必要的情况下使用未计算的结果
        val = curCSS( elem, name, styles );
        if ( val < 0 || val == null ) {
            val = elem.style[ name ];
        }

        //已计算的单元为非像素单位则终止并返回
        if ( rnumnonpx.test(val) ) {
            return val;
        }

        //我们需要检查style,避免浏览器使用getComputedStyle返回不可靠的值而悄悄的回到可靠的elem.style值
        valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );

        //规范“”,auto为拓展做准备
        val = parseFloat( val ) || 0;
    }

    //使用动态box-sizing模型来添加/减少不相干的样式
    return ( val +
        augmentWidthOrHeight(
            elem,
            name,
            extra || ( isBorderBox ? "border" : "content" ),
            valueIsBorderBox,
            styles
            )
        ) + "px";
}

//extra表示计算时要包括的部分
function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
    //在循环中遍历cssExpand时使用
    //其中cssExpand = [ "Top", "Right", "Bottom", "Left" ],
    var i = extra === ( isBorderBox ? "border" : "content" ) ?
        //如果我们有了正确的测量结果,避免增大,正常情况下会走这一步
        4 :
        //否则初始化为水平或垂直特征(property)
        name === "width" ? 1 : 0,

    val = 0;

    for ( ; i < 4; i += 2 ) {
        //两种盒模型都排除margin,如果计算要包括margin,则加上他
        if ( extra === "margin" ) {
            val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
        }

        //如果是border-box模型
        if ( isBorderBox ) {
            // border-box包括padding,如果我们需要内容部分因此要减去他
            if ( extra === "content" ) {
                val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
            }

            //此时,extra不是边框也非margin时,减去边框
            if ( extra !== "margin" ) {
                val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
            }
        //如果是content-box模型
        } else {
            //此时,extra不是内容,所以加上padding
            val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );

            //此时,extra不是内容也不是padding,所以加上边框
            if ( extra !== "padding" ) {
                val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
            }
        }
    }
    return val;
}

 

posted @ 2015-11-16 16:29  chua1989  阅读(795)  评论(0编辑  收藏  举报