jQuery-1.9.1源码分析系列(九) CSS操作
jquery.fn.css获取当前jQuery所匹配的元素中第一个元素的属性值【$(…).css(cssName),注意这个cssName可以是数组】或给当前jQuery所匹配的每个元素设置样式值【$(…).css(cssname,value) / $(…).css(obj)】;
可以看见函数内部直接调用了jquery.access来处理。access将当前多个元素组成的jQuery对象所匹配的元素分解成单一元素逐个调用第二个参数中的回调function( elem, name, value );如果参数name是对象的话,access内部分解name递归调用逐个处理name的每一个key/value键值对
源码
jQuery.fn.css: function( name, value ) {
//access将当前jQuery对象分解成单一元素逐个调用第二个参数中的回调function( elem, name, value ),
//如果参数name是对象的话,access内部分解name递归调用逐个处理name的每一个key/value键值对
return jQuery.access( this, function( elem, name, value ) {
var len, styles,
map = {},
i = 0;
//如果css特征名称是一个数组,比如['left','marginRight']
if ( jQuery.isArray( name ) ) {
styles = getStyles( elem );
len = name.length;
//通过$.css()获取对应的css特征值
for ( ; i < len; i++ ) {
map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
} return map;
} //value有值则调用$.style设置单个css值,value参数无值则通过$.css()获取对应的css特征值
return value !== undefined ?
jQuery.style( elem, name, value ) :
jQuery.css( elem, name );
}, name, value, arguments.length > 1 );
}
这个api比较简单,但是仔细分析里面所调用的函数会发现一大堆的知识。后面一一来发掘。
首先我们看到getStyles(elem)这个函数,看一看他的定义
if ( window.getComputedStyle ) {
getStyles = function( elem ) {
return window.getComputedStyle( elem, null );
};
...
//ie8-兼容
} else if ( document.documentElement.currentStyle ) {
getStyles = function( elem ) {
return elem.currentStyle;
};
...
}
两个不同的方法window.getComputedStyle和elem.currentStyle。接下来一一分析他们。
a. window.getComputedStyle
完整的表达式window.getComputedStyle(elem,pseudo)
elem: DOM节点,必须
pseudo: 伪类,且只能是伪元素的伪类,比如::after,::before,::first-letter,::first-line。可选【Gecko 2.0 (Firefox 4 / Thunderbird 3.3 / SeaMonkey 2.1) 之前,第二个参数“伪类”是必需的】
说明:这个函数取出来的是CSS计算后的最终使用的CSS属性值【其中不包括位置关系left/right/top/bottom的属性值,比如left:10%,那么通过getComputedStyle获取的left还是"10%"】(Safari 5.1.7的margin-right返回的是百分比,这个需要特殊处理)。只读。在现代浏览器中支持良好,不支持IE8-
举例说明(这个例子会贯穿全文):
<style>
#myList{
height: 100px;
}
#myList:after{
content: 'a';
height: 100px;
}
</style>
<ul id="myList" style='width:50%;left:666px;'><li>test</li></ul>
<ul id="myList2" style='width:50%;left:111px;'><li>test2</li></ul>
getComputedStyle的值是计算后的值(比如百分比会转换成像素),实例如下
宽度50%被转换成像素了。
getComputedStyle中第二个参数只有是伪元素的伪类才起作用,实例如下
如上图可知,只要后面的伪元素不对或没有传递伪元素伪类这个参数,则得到的还是第一个参数对应的元素的getComputedStyle值。上图中.left和.content属性可作证。
getComputedStyle得到的是一个只读数组对象,数组的每个元素是一个CSS样式名称,并且这个只读数组对象拥有与数组中每个元素一一对应的属性来保存CSS样式的值。我们先看一下结构(window.getComputedStyle(document.getElementById('myList'),null))
但是由于不支持IE8-那么我们需要一个IE8能支持的方法。这就是IE自己的东东elem.currentStyle
b. elem.currentStyle
elem.currentStyle也是一个只读的对象。
elem.currentStyle与getComputedStyle的不同
1. elem.currentStyle在IE8-中是一个纯对象,没有类数组的结构,不可以通过style[n]方式获得CSS样式名称;在IE9+中elem.currentStyle才是一个类数组对象。
2. elem.currentStyle获得的样式虽然是最终的CSS样式,但是并非计算过的样式,比如同样是先前的例子
getComputedStyle的width结果是像素值632px
而elem.currentStyle的结果是设置的百分比值50%
3. 样式名称的差异,比如对于float属性,IE8-中currentStyle是styleFloat
而firefox中getComputedStyle是cssFloat和float【建议不要使用,浏览器保留,而且也非标准方法,浏览器可以不支持】并存
chrome中getComputedStyle是显示是float,实际上通过float【建议不要使用,浏览器保留,而且也非标准方法,浏览器可以不支持】和cssFloat都可以获取到
而IE9是cssFloat和styleFloat都有。
所以jQuery获取float属性的方式是"float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
c. elem.style
elem.style和window.getComputedStyle(elem,pseudo)以及elem.currentStyle比较
elem.style是获取elem的内联样式。这是和window.getComputedStyle(elem,pseudo)以及elem.currentStyle相比不同的地方之一。
style结果可读可写,而getComputedStyle和currentStyle的结果只读。
style结果是没有计算的结果,这一点和currentStyle类似,getComputedStyle是计算过的结果。如下图中style.width的值是50%,而不是像素值
d. 现代浏览器获取样式表中某个样式的值--getPropertyValue
getPropertyValue(className)是现代浏览器(IE9+,firefox,chrome...)样式表的一个属性方法。所以只要是现代浏览器,上面getComputedStyle/currentStyle/style三种方式获取的样式表style都可以使用该方法获取样式值。例如style.getPropertyValue("float")。
需要注意的是className是直接属性名称(比如"background-color")。是“float”而非“css-float”或“cssFloat”。
如果是使用属性获取的方式获取float值,则需要转换成"cssFloat"或"styleFloat",比如
这个比较折腾。
而由于IE8-又不支持该方法,IE8-直接使用属性获取方式
IE8-的currentStyle还支持另一种获取属性方法:style.getAttribute(className)。
需要注意的是className是可以是直接属性名称或是驼峰写法(比如"background-color"或“backgroundColor"都可以)。
所以,我们为了兼容的话,有两种处理样式的方法
1. 结合getPropertyValue(className)和getAttribute(className)使用,因为他们两className都可以是直接属性名称
2. 使用属性获取方式style[className]但是需要注意的是属性名称需要兼容,比如:"float"需要替换成"cssFloat"或"styleFloat"。
而jQuery的处理就是选择第二种方式。
说道这里,虽然jQuery使用第二种方式,但是有一个属性使用属性方式获取会失败,这就是奇葩的"filter"属性。这个属性必须使用getPropertyValue才能获取正确
用表格总结一下CSS属性获取的相关用法
特点 | getComputedStyle | currentStyle | style |
浏览器支持情况 | IE9+,chrome,firefox... | IE | ALL |
可读可写 | 只读 | 只读 | 可读可写 |
是否为计算最终值(比如百分比、比例都计算为真实的像素值) | 是 | 否 | 否 |
(外部样式表+内部样式表+内联样式)的最终结果 | 是 | 是 | 否(只是内联样式) |
属性获取方式style.name和style[name] | 支持("filter"属性除外) | 支持 | 支持 |
支持的获取CSS属性值的方法 | getPropertyValue | IE8-只支持getAttribute;IE9+支持getPropertyValue和getAttribute("filter") | IE8-只支持getAttribute;IE9+支持getPropertyValue和getAttribute("filter");其他浏览器只支持getPropertyValue |
突出优点 | 标准化,支持伪元素(如::after),获取的是计算后的结果 | 可读可写 | |
这一章字数已经不少了,我先前还以为这一章会比较简单的,没有想到拓展开来这么多!骚年们,耐心看下去。。。
接下来继续分析源码:
既然了解了各种获取CSS样式表的方法,接下来看一个获取指定的CSS样式名称对应的计算值得函数curCSS = function( elem, name, _computed ) 。难点在于通过currentStyle和getComputedStyle获取到的值可能是百分比或相对值得时候,我们需要进行模拟计算出真实的值。过程比较点单,看源码注释
//备注:我们在window.getComputedStyle包含了"window"
//因为node.js中的js DOM如果没有他的话将被终止
if ( window.getComputedStyle ) {
getStyles = function( elem ) {
return window.getComputedStyle( elem, null );
}; curCSS = function( elem, name, _computed ) {
var width, minWidth, maxWidth,
computed = _computed || getStyles( elem ),
// getPropertyValue只有在IE9的 .css('filter')中用到
ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined,
style = elem.style; if ( computed ) {
if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
ret = jQuery.style( elem, name );
}
// Chrome < 17 and Safari 5.0使用"计算结果"替代"使用的值"来计算margin-right
// Safari 5.1.7 (最新)返回百分比但是我们需要像素值,这一点违背CSSOM草案
//http://dev.w3.org/csswg/cssom/#resolved-values
//模拟计算
if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
//保存原值
width = style.width;
minWidth = style.minWidth;
maxWidth = style.maxWidth; //放入新的值来获得计算值,比如marginRight为10%的时候,通过放入width为10%,然后通过computed.width即可获得10%对应的px宽度
style.minWidth = style.maxWidth = style.width = ret;
ret = computed.width; //还原更改的值
style.width = width;
style.minWidth = minWidth;
style.maxWidth = maxWidth;
}
}
return ret;
}; //低版本ie兼容
} else if ( document.documentElement.currentStyle ) {
getStyles = function( elem ) {
return elem.currentStyle;
}; curCSS = function( elem, name, _computed ) {
var left, rs, rsLeft,
computed = _computed || getStyles( elem ),
ret = computed ? computed[ name ] : undefined,
style = elem.style; //这里避免给ret设置空字符
//所以我们不默认为”auto”
if ( ret == null && style && style[ name ] ) {
ret = style[ name ];
} // 我们对以奇怪结尾的数字(比如1em)要把他转化成像素
// 但是不能是位置属性,应为他们是和父元素成比例的,并且我们不能测量父元素的比例,因为他可能是一大堆比例堆叠而成(比如<div style=’left:10%’><p style=’left:20%’><span style=’left:20%’></span></p></div>),根本无法计算
if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {
//保存原值
left = style.left;
rs = elem.runtimeStyle;
rsLeft = rs && rs.left; //放入新的值来获得计算值
if ( rsLeft ) {
rs.left = elem.currentStyle.left;
}
style.left = name === "fontSize" ? "1em" : ret;
ret = style.pixelLeft + "px"; //还原更改的值
style.left = left;
if ( rsLeft ) {
rs.left = rsLeft;
}
} return ret === "" ? "auto" : ret;
};
}
根据jQuery.fn.css函数最后的处理
return value !== undefined ?
jQuery.style( elem, name, value ) :
jQuery.css( elem, name );
可知css处理的两个关键低级api:jQuery.style和jQuery.css。
前面已经分析了只有.style是可读可写的,同理,这里的jQuery.style作用也是用来读写内联样式的。jQuery.style的处理流程为
1.修正css特征名称保存为origName,真正能被浏览器识别的名称保存为name。
2.查找name或origName的cssHooks。
3.如果是设置值(有传递value参数),则设置之。其中比较特殊的处理是value可以是累计字符串(如:“+=”)需要先通过jQuery.css取出原来的值来计算。如果value是数字,需要根据情况看是否添加“px”单位。如果是有相应的cssHooks也要特殊处理等等。
4.如果是获取值(没有传递value参数),分两种情况,有hooks通过hooks获取,否则直接style[name]即可。
源码
//给DOM节点设置或获取样式特征值
jQuery.style: function( elem, name, value, extra ) {
//文本和注释节点不能设置样式特征值
if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
return;
} //修正css特征名称以适配当前浏览器
var ret, type, hooks,
origName = jQuery.camelCase( name ),
style = elem.style;
//jQuery.cssProps缓存查询过的css特征名称供后续便捷查找使用
name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) ); //获取有前缀版本的hooks或没有前缀版本的hooks
hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; //如果是给css特征名称设置值
if ( value !== undefined ) {
type = typeof value; // convert relative number strings (+= or -=) to relative numbers. #7345
//rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" )
//转换相对数字符串+=/-=为相应的数字
if ( type === "string" && (ret = rrelNum.exec( value )) ) {
//(+ + 1) == 1;(- + 1) == -1
value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
// Fixes bug #9237
type = "number";
} //NaN和null不可用
if ( value == null || type === "number" && isNaN( value ) ) {
return;
} //除开不可设置为像素单位的css特征,其他的都添加上“px”
if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
value += "px";
} //support.clearCloneStyle = div.style.backgroundClip === "content-box";
//div.style.backgroundClip为非“content-box”模式且
//设置的值为空的background...将其设置为继承父节点样式
// Fixes #8908, 更准确的做法是对每一个问题特征都设置默认值,但是这至少会调用8次函数
if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) {
style[ name ] = "inherit";
} //如果提供了hook则使用hook设置值,否则设置置顶的值
if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) { //当要设置的值为无效值的时候ie会抛出异常
// Fixes bug #5509
try {
style[ name ] = value;
} catch(e) {}
}
//获取值
} else {
//如果提供了hook则使用hook取值
if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
return ret;
} // 其他情况从style对象中取值
return style[ name ];
}
} //返回一个css特征名称,该名称可能是供应商添加了前缀的特征名
function vendorPropName( style, name ) {
//短名称未添加厂商前缀
if ( name in style ) {return name; } //检查供应商的前缀名
//cssPrefixes = [ "Webkit", "O", "Moz", "ms" ]
var capName = name.charAt(0).toUpperCase() + name.slice(1),
origName = name,
i = cssPrefixes.length; while ( i-- ) {
name = cssPrefixes[ i ] + capName;
if ( name in style ) {return name; }
} return origName;
}
jQuery.css处理也比较简单
1.修正css特征名称保存为origName,真正能被浏览器识别的名称保存为name
2.如果存在相应的cssHooks,则处理之;否则使用curCSS方法获取样式值
3.对获取到的样式值做一些默认值得处理,比如css样式fontWeight的默认值为“normal”对应的值应该是400。
源码
//获取name对应的css特征值
css: function( elem, name, extra, styles ) {
var num, val, hooks,
origName = jQuery.camelCase( name ); //修正名称
name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) ); //获取有前缀版本的hooks或没有前缀版本的hooks
hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; //从hook中提取值
if ( hooks && "get" in hooks ) {
val = hooks.get( elem, true, extra );
} //其他情况使用curCSS取值
if ( val === undefined ) {
val = curCSS( elem, name, styles );
} //cssNormalTransform = {letterSpacing: 0,fontWeight: 400}
//将"normal"转化成计算值
if ( val === "normal" && name in cssNormalTransform ) {
val = cssNormalTransform[ name ];
} //当强制或提供了限定和val看上去像数字时强制转化成数字并返回
if ( extra === "" || extra ) {
num = parseFloat( val );
return extra === true || jQuery.isNumeric( num ) ? num || 0 : val;
}
return val;
}
OK,这一章终于搞定了。
如果觉得本文不错,请点击右下方【推荐】!
jQuery-1.9.1源码分析系列(九) CSS操作的更多相关文章
- jQuery源码分析系列(38) : 队列操作
Queue队列,如同data数据缓存与Deferred异步模型一样,都是jQuery库的内部实现的基础设施 Queue队列是animate动画依赖的基础设施,整个jQuery中队列仅供给动画使用 Qu ...
- jQuery 2.0.3 源码分析 钩子机制 - 属性操作
jQuery提供了一些快捷函数来对dom对象的属性进行存取操作. 这一部分还是比较简单的. 根据API这章主要是分解5个方法 .attr() 获取匹配的元素集合中的第一个元素的属性的值 或 设置 ...
- jQuery源码分析系列
声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...
- [转]jQuery源码分析系列
文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...
- jQuery源码分析系列(转载来源Aaron.)
声明:非本文原创文章,转载来源原文链接Aaron. 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAa ...
- jQuery源码分析系列——来自Aaron
jQuery源码分析系列——来自Aaron 转载地址:http://www.cnblogs.com/aaronjs/p/3279314.html 版本截止到2013.8.24 jQuery官方发布最新 ...
- jquery2源码分析系列
学习jquery的源码对于提高前端的能力很有帮助,下面的系列是我在网上看到的对jquery2的源码的分析.等有时间了好好研究下.我们知道jquery2开始就不支持IE6-8了,从jquery2的源码中 ...
- jQuery-1.9.1源码分析系列完毕目录整理
jQuery 1.9.1源码分析已经完毕.目录如下 jQuery-1.9.1源码分析系列(一)整体架构 jQuery-1.9.1源码分析系列(一)整体架构续 jQuery-1.9.1源码分析系列(二) ...
- MyCat源码分析系列之——结果合并
更多MyCat源码分析,请戳MyCat源码分析系列 结果合并 在SQL下发流程和前后端验证流程中介绍过,通过用户验证的后端连接绑定的NIOHandler是MySQLConnectionHandler实 ...
- MyCat源码分析系列之——SQL下发
更多MyCat源码分析,请戳MyCat源码分析系列 SQL下发 SQL下发指的是MyCat将解析并改造完成的SQL语句依次发送至相应的MySQL节点(datanode)的过程,该执行过程由NonBlo ...
随机推荐
- android——handler机制原理
在android版本4.0及之后的版本中多线程有明确的分工,子线程可以写所有耗时的代码(数据库.蓝牙.网络服务),但是绝对不能碰UI,想碰UI跟着主线程走,那么我们如何才能让主线程知道我们要对 UI进 ...
- Angular 1.x 升级到 Angular 2
原项目用ng1.5写的,现在改成ng2.0了,踩了不少坑,不过都忘记了. 如果你也正好要做这个工作,正好看到这个文章,不妨参考下. AngularJs 1.x -> 2.0 ng-repeat ...
- 用javascript实现一个2048游戏
早就想自己写一个2048游戏了,昨晚闲着没事,终于写了一个 如下图,按方向键开始玩吧. 如果觉得操作不方便,请直接打开链接玩吧: http://gujianbo.1kapp.com/2048/2048 ...
- NEsper Nuget包
Esper是专门进行复杂事件处理(CEP)的流处理平台,Java版本为Esper,.Net版本为NEsper.Esper & NEsper可以方便开发者快速开发部署处理大容量消息和事件的应用系 ...
- Android安全开发之安全使用HTTPS
Android安全开发之安全使用HTTPS 1.HTTPS简介 阿里聚安全的应用漏洞扫描器中有证书弱校验.主机名弱校验.webview未校验证书的检测项,这些检测项是针对APP采用HTTPS通信时容易 ...
- 负载均衡的场景下ASP.NET Core如何获取客户端IP地址
在ASP.NET中,使用负载均衡时,可以通过ServerVariables获取客户端的IP地址. var ip = request.ServerVariables["HTTP_X_FORWA ...
- 激活jws.mono的图像处理
不得不说,jws.mono真的给我们带来了很大的便利,它免除了我们编译Linux.NET所带来的烦恼,节省了我们的时间.但是金无足赤人无完人,虽然jws.mono已经大致能够提供与我们自行编译相同的效 ...
- jqGrid的autoencode参数设置为true在客户端可能引发的编码问题
不久前使用jqGrid+MVC做过一段时间开发. 一开始,分页参数几乎都是默认值,jqGrid的分页功能很好用. 考虑到each input is evil,我们的系统对安全性又有较高要求,所以,为了 ...
- iOS-网络基础
概览 大部分应用程序都或多或少会牵扯到网络开发,例如说新浪微博.微信等,这些应用本身可能采用iOS开发,但是所有的数据支撑都是基于后台网络服务器的.如今,网络编程越来越普遍,孤立的应用通常是没有生命力 ...
- java IO流 之 字节流
一.file类的常用操作 File file=new File("E:\\test\\javaIo"); System.out.println(file.isDirectory() ...