[原创] jQuery源码分析-13 CSS操作-CSS-样式表-jQuery.fn.css()
作者:nuysoft/高云 QQ:47214707 Email:nuysoft@gmail.com
声明:本文为原创文章,如需转载,请注明来源并保留原文链接。
样式表
jQuery.style( elem, name, value, extra )
jQuery.css( elem, name, 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 } // 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能熟练使用但不精通,看源码的过程遇到很多疑问,不懂的地方文中有标记,各位同学如果能解疑或有好的参考资料多多拍砖。本人、本文、本系列均不是权威,请持怀疑态度。