样式表

 

概述

如何使用

使用详解

特性对应的实现原理

源码分析

.css( name, value )

jQuery.style( elem, name, value, extra )

jQuery.css( elem, name, extra )

curCSS( elem, name )

 

概述

 

CSS操作部分的源码分析基于版本1.7.1,以后的jQuery源码分析系列将采用最新的版本。

jQuery.fn.css()主要解决了三个问题:

浏览器兼容:IE、W3C

兼容HTML样式属性和DOM样式属性:连词符、驼峰

使设置元素的样式属性变得快速简单:动态参数检测、设置和读取用统一的接口

 

如何使用

 

jQuery.fn.css()有4种用法,第一种是读取样式属性值,其余三种是设置样式属性值

.css( propertyName ) 获取第一个元素的样式属性值,propertyName是CSS属性名

.css( propertyName, value ) 在匹配的元素集上设置一个CSS属性,value是要设置的属性值

.css( propertyName, function(index, value) ) 将函数返回值做为属性值设置

function(index, value) 返回要设置的属性值,函数的上下文this指向当前元素,接收两个参数:index是当前元素在集合中的下标位置;value是旧值,即当前值(意味着设置之前要先取出当前值)

.css( map ) 设置多个样式

map 含有键值对的map,键是属性名,值是字面直接量或函数

 

使用详解

读取和设置样式表遇到的难题是浏览器兼容性。例如访问样式属性时的方式就不同

// 在基于标准的浏览器中
var defaultView = elem && elem.ownerDocument.defaultView;
var computedStyle = defaultView && defaultView.getComputedStyle( elem, null );
var ret = computedStyle && computedStyle.getPropertyValue( name );
return ret;
// 相对的在IE中
var ret = elem.currentStyle && elem.currentStyle[ name ]
return ret;

另一个常见的兼容问题是,某些属性在不同的浏览器中使用不同的属性名,例如float,在IE的DOM实现中用styleFloat,而在遵守W3C标准的浏览器中是 cssFloat。

jQuery的.css()方法封装了这些差异,无论使用什么属性名都返回相同的结果。例如,一个向左浮动的元素,下边的三行代码每行都会返回字符串left:

$('div.left').css('float');
$('div.left').css('cssFloat');
$('div.left').css('styleFloat');

如果遇到由多个单词组成的属性,这些属性在CSS和DOM有着不一样的格式,jQuery也能等价的解释,例如:

.css( { 'background-color': '#ffe', 'border-left': '5px solid #ccc' } )
.css( { backgroundColor: '#ffe', borderLeft: '5px solid #ccc' } )

jQuery都能识别并返回正确的值,注意在DOM属性的引号是可选的,而CSS属性必须有引号,因为在属性名中有连字符-

当使用.css()设置样式时,jQuery改变元素的样式属性style property,例如下面两行代码是等价的:

$('#mydiv').css('color', 'green')
document.getElementById('mydiv').style.color = 'green'

为样式属性设置一个空字符串,例如$(‘#mydiv’).css(‘color’, ”),如果这个属性是行内样式(HTML style attribute),这个属性会被从元素的style中移除,无论是通过.css()方法操作,还是直接操作DOM样式属性style(DOM style property);但是如果是定义在外部样式表stylesheet或内部样式表<style>元素中则不会移除(jQuery的实现并不会修改外部样式表和内部样式表,这一点并不像有些书上写的,尽管浏览器提供了原生API支持)。

如果遇到CSS color,不同的浏览器可能返回逻辑上相等但是字面上不同的颜色值,总共有四种格式:#FFF、#ffffff、rgb(255,255,255)、blue。

但是.css()不支持CSS属性缩写,例如margin background border。例如,如果想要获取外边距,需要使用$(elem).css(‘marginTop’) 和 $(elem).css(‘marginRight’),其他以此类推。

从jQuery1.6开始,.css()可以支持相对值,就像.animate()。相对值是以+=或-=开头的字符串,表示对当前值增加或减少。例如:一个元素的padding-left是10px,.css(“padding-left”, “+=15”)使padding-left变为25px。

从jQuery1.4开始,.css()允许传入一个函数作为属性值,例如在下面的这个例子中,将匹配元素的宽度设置为不断增大的值(递增):

$('elem.example').css('width', function(index) {
  return index * 50;
});

注意:如果函数没有返回任何值(例如function( index, style ){}),或返回undefined,当前值不会改变。这一点很有用,如果需要只要当满足一定条件时,选择性的设置属性值时(函数不返回值或返回undefined,与返回空字符串,有着截然不同的处理逻辑和结果)。

最后补充一点CSS的基础知识,参考http://wenku.baidu.com/view/d9a18f7e27284b73f2425089.html:

1. CSS样式表有三种写法:行内样式、文档内部样式、文档外部样式

2. 样式优先级:行内样式> 内部样式 > 外部样式,ID选择器 > class选择器

 

将以上特性对应的实现原理简单阐述下(后边的源码分析会详细的解释):

设置和读取用都通过.css()

通过调用多功能工具函数jQuery.access(详见03 构造jQuery对象-工具函数

访问样式属性时的方式不同

jQuery加载执行时检测浏览器特性,将getComputedStyle或currentStyle统一为jQuery内部方法curCSS()

某些属性在不同的浏览器中使用不同的属性名

jQuery.cssProps中定义了属性名之间的映射关系

多个单词组成的样式属性在CSS和DOM有着不一样的格式

通过方法jQuery.camelCase()将连词符格式转为驼峰格式

相对值

通过jQuery内部正则rrelNum = /^([\-+])=([\-+.\de]+)/检测并提取运算符和相对值,然后计算

函数的返回值作为属性值

通过调用多功能工具函数jQuery.access执行函数,并将返回值传给jQuery.style

 

源码分析

 

.css( name, value )

jQuery.fn.css = function( name, value ) {
	// Setting 'undefined' is a no-op
	// 两个参数,value为undefined,则不做任何操作,返回this
	// 即如果将一个样式属性设为undefined,不做任何操作
	if ( arguments.length === 2 && value === undefined ) {
		return this;
	}
	// access: function( elems, key, value, exec, fn, pass ) {
	// 调用多功能工具函数jQuery.access,对this进行遍历,并执行参数中的函数
	return jQuery.access( this, name, value, true, function( elem, name, value ) {
		return value !== undefined ?
			jQuery.style( elem, name, value ) : // 设值,包括value是空字符串
			jQuery.css( elem, name ); // 取值

	});
};

.css()依赖于三个方法:

jQuery.access() 这个全局方法支持.css()、.attr()、.prop(),分析详见03 构造jQuery对象-工具函数

jQuery.style() 在DOM节点上读取或设置样式属性(style property)

jQuery.css() 在DOM元素上读取DOM样式值

马上开始剖析jQuery.style()和jQuery.css()。

 

jQuery.style( elem, name, value, extra )

 

jQuery.style()负责在DOM节点上读取或设置样式属性style property(事实上在CSS模块中只用了jQuery.style()的设置功能,读取功能和jQuery.css()有什么区别么?有待继续研究!)。

这个方法大致做了如下事

1. 过滤Text和Comment,过滤无style的元素,返回undefined

2. 转换为驼峰式,修正属性名

3. 如果是设置:

如果是number,过滤NaN;过滤null;如果是相对值字符串,计算

添加后缀

如果存在钩子,则调用钩子的set;如果没有钩子,则设置style[ name ] = value;

4. 如果是读取:

如果存在钩子,则调用钩子的get;如果没有钩子,则返回style[ name ]

看看源码注释

// Get and set the style property on a DOM Node
// 在DOM节点上读取或设置样式属性style property
style: function( elem, name, value, extra ) {
	// Don't set styles on text and comment nodes
	// 过滤Text和Comment,如果没有style属性也返回
	if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
		return; // undefined
	}

	// Make sure that we're working with the right name
	// 确保使用了正确的名字
	var ret, type, origName = jQuery.camelCase( name ),
		style = elem.style, hooks = jQuery.cssHooks[ origName ]; // 转换为驼峰格式
	// 修正属性名,是否在不同的浏览器中使用不同的属性名
	name = jQuery.cssProps[ origName ] || origName; // CSS钩子

	// Check if we're setting a value
	// 设置
	if ( value !== undefined ) {
		type = typeof value;

		// convert relative number strings (+= or -=) to relative numbers. #7345
		// 计算相对值 rrelNum = /^([\-+])=([\-+.\de]+)/, //
		if ( type === "string" && (ret = rrelNum.exec( value )) ) {
			/*
		 	* ret[1] 正负;ret[2] 相对值
		 	* +( ret[1] + 1) ret[1]是字符串,加上1变成'+1'或'-1',最前边的加号将字符串转换为数字1或-1
			 * +ret[2] 同样的加号将ret[2]转换为数字
		 	* 正负1 乘以 相对值 再加上 当前值,得出要设置的值
			 */
			value = ( +( ret[1] + 1) * +ret[2] ) + parseFloat( jQuery.css( elem, name ) );
			// Fixes bug #9237
			// #9237:.css()在带有连字符的属性上不能工作,在1.6.2中修正
			type = "number";
		}

		// Make sure that NaN and null values aren't set. See: #7116
		// 过滤NaN null,不做任何处理,如果想从内联样式中删除某个属性,请传入空字符串
		if ( value == null || type === "number" && isNaN( value ) ) {
			return;
		}

		// If a number was passed in, add 'px' to the (except for certain CSS properties)
		// 如果传入一个数字,追加单位px(jQuery.cssNumber中定义的属性除外,见jQuery.cssNumber的定义)
		if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
			value += "px";
		}

		// 前边的都是前戏:过滤非法参数、计算相对值、追加单位后缀

		// If a hook was provided, use that value, otherwise just set the specified value
		/*
		 * 如果有钩子hooks,且hooks中存在set函数,则调用hooks.set,将返回值赋给value
		 * 如果hooks.set的返回值为undefined,则不执行任何操作;返回值不为undefined,则用新value设置样式值
		 * 简单点说,有hooks.set则调用,用返回值替换value,最后设置style.name;否则直接设置style.name
		 * 可见钩子的作用是修正属性值,并不直接对值进行设置
		 * 等价的逻辑:
		 * <pre>
		 * if ( hooks && "set" in hooks ) {
		 *   value = hooks.set( elem, value );
		 *   if( value != undefined ) style[ name ] = value;
		 * } else {  
		 *   style[ name ] = value;
		 * }
		 * </pre>
		 */
		if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
			// Wrapped to prevent IE from throwing errors when 'invalid' values are provided
			// Fixes bug #5509
			// 用try-catch块,预防在IE中,当用不合法的值设置样式值时,抛出异常
			try {
				style[ name ] = value;
			} catch(e) {}
		}
	// 读取
	} else {
		// If a hook was provided get the non-computed value from there
		// 如果有钩子hooks,则调用hooks.get,返回值赋给ret
		if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
			return ret;
		}

		// Otherwise just get the value from the style object
		// 否则从style对象中读取属性值
		return style[ name ];
	}
}

 

jQuery.css( elem, name, extra )

 

jQuery.css() 负责读取样式值。

这个方法大致做了如下事

1. 转换为驼峰式,修正属性名

2. 如果有钩子,则调用钩子的get

3. 否则调用curCSS,不同的浏览器调用不同的方法:

IE:getComputedStyle,elem.ownerDocument.defaultView.getComputedStyle( elem, null ).getPropertyValue( name )

W3C:currentStyle,elem.currentStyle[ name ]

看看源码注释

// 读取样式值
css: function( elem, name, extra ) {
	var ret, hooks;

	// Make sure that we're working with the right name
	name = jQuery.camelCase( name ); // 转换为驼峰式
	hooks = jQuery.cssHooks[ name ]; // 是否有钩子
	name = jQuery.cssProps[ name ] || name; // 修正属性名

	// cssFloat needs a special treatment
	// cssFloat需要特殊处理,(styleFloat不需要吗?)
	if ( name === "cssFloat" ) {
		name = "float"; // 又把它转换回去了!
	}

	// If a hook was provided get the computed value from there
	// 如果钩子hooks存在,则调用hooks.get计算样式值,并返回
	if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {
		return ret;

	// Otherwise, if a way to get the computed value exists, use that
	// 否则,如果curCSS存在,则调用curCSS获取计算后的样式值,并返回
	} else if ( curCSS ) {
		return curCSS( elem, name );
	}
}

 

curCSS( elem, name )

 

/**
 * 标准
 */
if ( document.defaultView && document.defaultView.getComputedStyle ) {
	getComputedStyle = function( elem, name ) {
		var ret, defaultView, computedStyle; // 预定义变量
		// 将驼峰式转换为连字符,例如marginTop > margin-top
		// rupper = /([A-Z]|^ms)/g,
		name = name.replace( rupper, "-$1" ).toLowerCase();

		/*
		 * 分解:
		 * var defaultView = elem && elem.ownerDocument.defaultView;
		 * var computedStyle = defaultView && defaultView.getComputedStyle( elem, null );
		 * var ret = computedStyle && computedStyle.getPropertyValue( name );
		 * return ret;
		 */
		if ( (defaultView = elem.ownerDocument.defaultView) &&
				(computedStyle = defaultView.getComputedStyle( elem, null )) ) {
			ret = computedStyle.getPropertyValue( name );
			// 看不懂这行在干什么?
			if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
				ret = jQuery.style( elem, name );
			}
		}

		return ret;
	};
}
/**
 * IE
 */
if ( document.documentElement.currentStyle ) {
	currentStyle = function( elem, name ) {
		var left, rsLeft, uncomputed,
			ret = elem.currentStyle && elem.currentStyle[ name ], // 直接就取值
			style = elem.style;

		// Avoid setting ret to empty string here
		// so we don't default to auto
		/*
		 * 避免返回空字符串,看不懂?
		 * 如果elem.currentStyle[ name ]返回null,用style[name]试试
		 */
		if ( ret === null && style && (uncomputed = style[ name ]) ) {
			ret = uncomputed;
		}

		// From the awesome hack by Dean Edwards
		// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291

		// If we're not dealing with a regular pixel number
		// but a number that has a weird ending, we need to convert it to pixels
		/*
		 * 不处理一般的像素值,但是如果单位很奇怪就需要修正为像素px
		 * rnumpx = /^-?\d+(?:px)?$/i, // 可选的负号 加 数字 加 可选的px,对数值进行检查
		 * rnum = /^-?\d/, // 整数,不支持+1这样的写法(应该支持)
		 * 
		 * 数字后跟了非像素单位
		 * 
		 * 后边的看不懂啊,应该是修正单位、auto、fontSize
		 */
		if ( !rnumpx.test( ret ) && rnum.test( ret ) ) {

			// Remember the original values
			// 记录原始值
			left = style.left;
			rsLeft = elem.runtimeStyle && elem.runtimeStyle.left; // 

			// Put in the new values to get a computed value out
			if ( rsLeft ) { 
				elem.runtimeStyle.left = elem.currentStyle.left;
			}
			style.left = name === "fontSize" ? "1em" : ( ret || 0 );
			ret = style.pixelLeft + "px";

			// Revert the changed values
			style.left = left;
			if ( rsLeft ) {
				elem.runtimeStyle.left = rsLeft;
			}
		}

		return ret === "" ? "auto" : ret;
	};
}

curCSS = getComputedStyle || currentStyle;

 

后记:本人对CSS能熟练使用但不精通,看源码的过程遇到很多疑问,不懂的地方文中有标记,各位同学如果能解疑或有好的参考资料多多拍砖。本人、本文、本系列均不是权威,请持怀疑态度。

发表评论

邮箱地址不会被公开。 必填项已用*标注