[原创] jQuery源码分析-13 CSS操作-CSS-样式表-jQuery.fn.css()
作者:nuysoft/ 高云 QQ:47214707 Email:nuysoft@gmail.com
声明:本文为原创文章,如需转载,请注明来源并保留原文链接。
样式表
jQuery.style(elem, name, value, extra)
概述
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 }<span style="color: rgba(0, 128, 0, 1)">// Make sure that we're working with the right name</span> <span style="color: rgba(0, 128, 0, 1)">// 确保使用了正确的名字</span> <span style="color: rgba(0, 0, 255, 1)">var</span> ret, type, origName = jQuery.camelCase( <span style="color: rgba(0, 0, 255, 1)">name</span> ), style = elem.style, hooks = jQuery.cssHooks[ origName ]; <span style="color: rgba(0, 128, 0, 1)">// 转换为驼峰格式</span> <span style="color: rgba(0, 128, 0, 1)">// 修正属性名,是否在不同的浏览器中使用不同的属性名</span> <span style="color: rgba(0, 0, 255, 1)">name</span> = jQuery.cssProps[ origName ] || origName; <span style="color: rgba(0, 128, 0, 1)">// CSS钩子</span> <span style="color: rgba(0, 128, 0, 1)">// Check if we're setting a value</span> <span style="color: rgba(0, 128, 0, 1)">// 设置</span> <span style="color: rgba(0, 0, 255, 1)">if</span> ( value !== undefined ) { type = <span style="color: rgba(0, 0, 255, 1)">typeof</span> value; <span style="color: rgba(0, 128, 0, 1)">// convert relative number strings (+= or -=) to relative numbers. #7345</span> <span style="color: rgba(0, 128, 0, 1)">// 计算相对值 rrelNum = /^([\-+])=([\-+.\de]+)/, //</span> <span style="color: rgba(0, 0, 255, 1)">if</span> ( type === "<span style="color: rgba(139, 0, 0, 1)">string</span>" && (ret = rrelNum.exec( value )) ) { <span style="color: rgba(0, 128, 0, 1)">/* * ret[1] 正负;ret[2] 相对值 * +( ret[1] + 1) ret[1]是字符串,加上1变成'+1'或'-1',最前边的加号将字符串转换为数字1或-1 * +ret[2] 同样的加号将ret[2]转换为数字 * 正负1 乘以 相对值 再加上 当前值,得出要设置的值 */</span> value = ( +( ret[1] + 1) * +ret[2] ) + <span style="color: rgba(0, 0, 255, 1)">parseFloat</span>( jQuery.css( elem, <span style="color: rgba(0, 0, 255, 1)">name</span> ) ); <span style="color: rgba(0, 128, 0, 1)">// Fixes bug #9237</span> <span style="color: rgba(0, 128, 0, 1)">// #9237:.css()在带有连字符的属性上不能工作,在1.6.2中修正</span> type = "<span style="color: rgba(139, 0, 0, 1)">number</span>"; } <span style="color: rgba(0, 128, 0, 1)">// Make sure that NaN and null values aren't set. See: #7116</span> <span style="color: rgba(0, 128, 0, 1)">// 过滤NaN null,不做任何处理,如果想从内联样式中删除某个属性,请传入空字符串</span> <span style="color: rgba(0, 0, 255, 1)">if</span> ( value == <span style="color: rgba(0, 0, 255, 1)">null</span> || type === "<span style="color: rgba(139, 0, 0, 1)">number</span>" && <span style="color: rgba(0, 0, 255, 1)">isNaN</span>( value ) ) { <span style="color: rgba(0, 0, 255, 1)">return</span>; } <span style="color: rgba(0, 128, 0, 1)">// If a number was passed in, add 'px' to the (except for certain CSS properties)</span> <span style="color: rgba(0, 128, 0, 1)">// 如果传入一个数字,追加单位px(jQuery.cssNumber中定义的属性除外,见jQuery.cssNumber的定义)</span> <span style="color: rgba(0, 0, 255, 1)">if</span> ( type === "<span style="color: rgba(139, 0, 0, 1)">number</span>" && !jQuery.cssNumber[ origName ] ) { value += "<span style="color: rgba(139, 0, 0, 1)">px</span>"; } <span style="color: rgba(0, 128, 0, 1)">// 前边的都是前戏:过滤非法参数、计算相对值、追加单位后缀</span> <span style="color: rgba(0, 128, 0, 1)">// If a hook was provided, use that value, otherwise just set the specified value</span> <span style="color: rgba(0, 128, 0, 1)">/* * 如果有钩子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> */</span> <span style="color: rgba(0, 0, 255, 1)">if</span> ( !hooks || !("<span style="color: rgba(139, 0, 0, 1)">set</span>" <span style="color: rgba(0, 0, 255, 1)">in</span> hooks) || (value = hooks.set( elem, value )) !== undefined ) { <span style="color: rgba(0, 128, 0, 1)">// Wrapped to prevent IE from throwing errors when 'invalid' values are provided</span> <span style="color: rgba(0, 128, 0, 1)">// Fixes bug #5509</span> <span style="color: rgba(0, 128, 0, 1)">// 用try-catch块,预防在IE中,当用不合法的值设置样式值时,抛出异常</span> <span style="color: rgba(0, 0, 255, 1)">try</span> { style[ <span style="color: rgba(0, 0, 255, 1)">name</span> ] = value; } <span style="color: rgba(0, 0, 255, 1)">catch</span>(e) {} } <span style="color: rgba(0, 128, 0, 1)">// 读取</span> } <span style="color: rgba(0, 0, 255, 1)">else</span> { <span style="color: rgba(0, 128, 0, 1)">// If a hook was provided get the non-computed value from there</span> <span style="color: rgba(0, 128, 0, 1)">// 如果有钩子hooks,则调用hooks.get,返回值赋给ret</span> <span style="color: rgba(0, 0, 255, 1)">if</span> ( hooks && "<span style="color: rgba(139, 0, 0, 1)">get</span>" <span style="color: rgba(0, 0, 255, 1)">in</span> hooks && (ret = hooks.get( elem, <span style="color: rgba(0, 0, 255, 1)">false</span>, extra )) !== undefined ) { <span style="color: rgba(0, 0, 255, 1)">return</span> ret; } <span style="color: rgba(0, 128, 0, 1)">// Otherwise just get the value from the style object</span> <span style="color: rgba(0, 128, 0, 1)">// 否则从style对象中读取属性值</span> <span style="color: rgba(0, 0, 255, 1)">return</span> style[ <span style="color: rgba(0, 0, 255, 1)">name</span> ]; }
}
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();<span style="color: rgba(0, 128, 0, 1)">/* * 分解: * var defaultView = elem && elem.ownerDocument.defaultView; * var computedStyle = defaultView && defaultView.getComputedStyle( elem, null ); * var ret = computedStyle && computedStyle.getPropertyValue( name ); * return ret; */</span> <span style="color: rgba(0, 0, 255, 1)">if</span> ( (defaultView = elem.ownerDocument.defaultView) && (computedStyle = defaultView.getComputedStyle( elem, <span style="color: rgba(0, 0, 255, 1)">null</span> )) ) { ret = computedStyle.getPropertyValue( <span style="color: rgba(0, 0, 255, 1)">name</span> ); <span style="color: rgba(0, 128, 0, 1)">// 看不懂这行在干什么?</span> <span style="color: rgba(0, 0, 255, 1)">if</span> ( ret === "<span style="color: rgba(139, 0, 0, 1)"></span>" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) { ret = jQuery.style( elem, <span style="color: rgba(0, 0, 255, 1)">name</span> ); } } <span style="color: rgba(0, 0, 255, 1)">return</span> ret; };
}
/**
-
IE
*/
if ( document.documentElement.currentStyle ) {
currentStyle = function( elem, name ) {
var left, rsLeft, uncomputed,
ret = elem.currentStyle && elem.currentStyle[ name ], // 直接就取值
style = elem.style;<span style="color: rgba(0, 128, 0, 1)">// Avoid setting ret to empty string here</span> <span style="color: rgba(0, 128, 0, 1)">// so we don't default to auto</span> <span style="color: rgba(0, 128, 0, 1)">/* * 避免返回空字符串,看不懂? * 如果elem.currentStyle[ name ]返回null,用style[name]试试 */</span> <span style="color: rgba(0, 0, 255, 1)">if</span> ( ret === <span style="color: rgba(0, 0, 255, 1)">null</span> && style && (uncomputed = style[ <span style="color: rgba(0, 0, 255, 1)">name</span> ]) ) { ret = uncomputed; } <span style="color: rgba(0, 128, 0, 1)">// From the awesome hack by Dean Edwards</span> <span style="color: rgba(0, 128, 0, 1)">// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291</span> <span style="color: rgba(0, 128, 0, 1)">// If we're not dealing with a regular pixel number</span> <span style="color: rgba(0, 128, 0, 1)">// but a number that has a weird ending, we need to convert it to pixels</span> <span style="color: rgba(0, 128, 0, 1)">/* * 不处理一般的像素值,但是如果单位很奇怪就需要修正为像素px * rnumpx = /^-?\d+(?:px)?$/i, // 可选的负号 加 数字 加 可选的px,对数值进行检查 * rnum = /^-?\d/, // 整数,不支持+1这样的写法(应该支持) * * 数字后跟了非像素单位 * * 后边的看不懂啊,应该是修正单位、auto、fontSize */</span> <span style="color: rgba(0, 0, 255, 1)">if</span> ( !rnumpx.test( ret ) && rnum.test( ret ) ) { <span style="color: rgba(0, 128, 0, 1)">// Remember the original values</span> <span style="color: rgba(0, 128, 0, 1)">// 记录原始值</span> left = style.left; rsLeft = elem.runtimeStyle && elem.runtimeStyle.left; <span style="color: rgba(0, 128, 0, 1)">// </span> <span style="color: rgba(0, 128, 0, 1)">// Put in the new values to get a computed value out</span> <span style="color: rgba(0, 0, 255, 1)">if</span> ( rsLeft ) { elem.runtimeStyle.left = elem.currentStyle.left; } style.left = <span style="color: rgba(0, 0, 255, 1)">name</span> === "<span style="color: rgba(139, 0, 0, 1)">fontSize</span>" ? "<span style="color: rgba(139, 0, 0, 1)">1em</span>" : ( ret || 0 ); ret = style.pixelLeft + "<span style="color: rgba(139, 0, 0, 1)">px</span>"; <span style="color: rgba(0, 128, 0, 1)">// Revert the changed values</span> style.left = left; <span style="color: rgba(0, 0, 255, 1)">if</span> ( rsLeft ) { elem.runtimeStyle.left = rsLeft; } } <span style="color: rgba(0, 0, 255, 1)">return</span> ret === "<span style="color: rgba(139, 0, 0, 1)"></span>" ? "<span style="color: rgba(139, 0, 0, 1)">auto</span>" : ret;
};
}
curCSS = getComputedStyle || currentStyle;
后记:本人对 CSS 能熟练使用但不精通,看源码的过程遇到很多疑问,不懂的地方文中有标记,各位同学如果能解疑或有好的参考资料多多拍砖。本人、本文、本系列均不是权威,请持怀疑态度。