一,基本结构
css模块,在fn上只提供了四个方法css,show,hide和toggle。可能有人会问为什么官方文档上还有innerHeight,scrollTop,removeClass之类的方法?其实这些方法不是在css模块中提供的,只是在api中把他们都放到了css中,innerHeight是在dimensions中提供的,hasClass是在attributes/classes中提供的。css模块提供了两个非常重要的静态方法$.css和$.style,大部分的其他函数最终都是通过调用这两个函数来实现的。
另外还有一个需要注意的是$.cssHooks,用来定义对不同属性的不同处理方式,很像c#中的getter/setter,比如height和width是要计算出来而不是直接取。
代码基本结构大概是这样的:
function vendorPropName( style, name ) { //检测浏览器前缀}
function getStyles( elem ) {//get computed style}
function showHide( elements, show ) {//显示隐藏元素,使用data_priv来保存display}
jQuery.fn.extend({//拓展四个方法
css: function() {}
show: function() {}
hide: function() {}
toggle: function() {}
})
jQuery.extend({ //几个静态方法,最重要的两个静态方法css和style就是在这里定义的
cssHooks: function() {}
cssNumber: function() {}
style: function() {}
css: function() {}
})
//下面是几个工具方法
curCSS = function( elem, name, _computed ) {}
function augmentWidthOrHeight() {}
function getWidthOrHeight( elem, name, extra ) {
//下面是对height和width的hook
jQuery.each([ "height", "width" ], function( i, name ) {})
…
二,jQuery.css和jQuery.style
这两个函数的实现都比较简单,最大的区别是:
style是计算元素自身的样式,不包括从父元素继承来的样式,而且style包含读写操作
css返回的是computedStyle,也就是包括父元素的样式,并且css只能读不能写(computedStyle是计算结果而不是一个属性,显然没法修改)
style: function( elem, name, value, extra ) {
//style方法是读写元素的自身样式,而不计算继承来的样式
// Don't set styles on text and comment nodes
if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
return;
}
// Make sure that we're working with the right name
var ret, type, hooks,
origName = jQuery.camelCase( name ),
style = elem.style;
//检测浏览器前缀,并且会缓存在jQuery.cssProps中
name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
// gets hook for the prefixed version
// followed by the unprefixed version
//取hook
hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
// Check if we're setting a value
if ( value !== undefined ) {
type = typeof value;
//下面三个if都是对数字的处理
// convert relative number strings (+= or -=) to relative numbers. #7345
//把字符串转成数字
if ( type === "string" && (ret = rrelNum.exec( value )) ) {
value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
// Fixes bug #9237
type = "number";
}
// Make sure that NaN and null values aren't set. See: #7116
if ( value == null || type === "number" && isNaN( value ) ) {
return;
}
// If a number was passed in, add 'px' to the (except for certain CSS properties)
//如果name不在jQuery.cssNumber中,则自动追加’px’,所以,可以通过在jQuery.cssNumber中添加属性的方式来修改这一行为
if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
value += "px";
}
// Fixes #8908, it can be done more correctly by specifying setters in cssHooks,
// but it would mean to define eight (for every problematic property) identical functions
//自动把没有显示声明的属性改成’inherit'
if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) {
style[ name ] = "inherit";
}
// If a hook was provided, use that value, otherwise just set the specified value
// 如果有hook,则调用hook中的set方法来设置value
if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
style[ name ] = value;
}
} else {
// If a hook was provided get the non-computed value from there
//如果有hook,则调用hook中的get方法来取value
if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
return ret;
}
// Otherwise just get the value from the style object
return style[ name ];
}
},
css: function( elem, name, extra, styles ) {
// css 是读取计算后的样式
var val, num, hooks,
origName = jQuery.camelCase( name );
// Make sure that we're working with the right name
//和style中是一样的,检测浏览器前缀
name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
// gets hook for the prefixed version
// followed by the unprefixed version
hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
// If a hook was provided get the computed value from there
// 使用hook.get
if ( hooks && "get" in hooks ) {
val = hooks.get( elem, true, extra );
}
// Otherwise, if a way to get the computed value exists, use that
if ( val === undefined ) {
// 调用curCSS,得到的是计算后的样式
val = curCSS( elem, name, styles );
}
//convert "normal" to computed value
if ( val === "normal" && name in cssNormalTransform ) {
val = cssNormalTransform[ name ];
}
// Return, converting to number if forced or a qualifier was provided and val looks numeric
if ( extra === "" || extra ) {
num = parseFloat( val );
return extra === true || jQuery.isNumeric( num ) ? num || 0 : val;
}
return val;
}
});
三,jQuery.cssHooks
因为有些样式不是简单的读写属性就可以的,比如width就不是简单地读取el.style.width。为了解决这个问题,jquery定义了一个属性 $.cssHooks,这里可以自定义对某个属性的get和set操作。而且jquery中就是用cssHooks来处理某些特殊属性的,比如下面这个:jQuery.each([ "height", "width" ], function( i, name ) {
jQuery.cssHooks[ name ] = {
get: function( elem, computed, extra ) {
if ( computed ) {
// certain elements can have dimension info if we invisibly show them
// however, it must have a current display style that would benefit from this
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
);
}
};
});
cssHooks很适合用来写css插件,比如你想自定义一个插件 abc,可以这样写:
jQuery.cssHooks[ ‘abc' ] = {
get: function(elem, computed, extra) {
return ' I am abc'
}
}
最后还有一个show/hide是比较特殊的,她用到了data_priv来保存原始display,所以不会在切换的时候丢掉display属性。