javascript 精确获取样式属性(上)

JQuery,mootools,Ext等类库在这部分实现得非常艰辛,盘根错节地动用一大堆方法,因此想把这部分抠出来难度很大。深入研究它们的实现后,根据我积累的CSS知识,终于做出一个非常简炼的版本出来。它相当于JQuery.cssCur吧,不过或许功能还丰富一些,按饮食业话说叫“加量不加价”,我的可能还应叫“加量还减价”……版本还处于Beta阶段,由于只个工具函数就不弄成类了。


复制代码 代码如下:

var getStyle = function(el, style){
if(!+"\v1"){
style = style.replace(/\-(\w)/g, function(all, letter){
return letter.toUpperCase();
});
return el.currentStyle[style];
}else{
return document.defaultView.getComputedStyle(el, null).getPropertyValue(style)
}
}

这是函数的最原始状态,由于used value是W3C那边的人搞出来的,因此document.defaultView.getComputedStyle 基本连动也不动就解决百分之99的问题。IE那边的复杂了,虽然微软搞了style,currentStyle与runtimeStyle,但始终都没有一个与getComputedStyle相近的实现,最相近的是currentStyle,它只能取到内部样式,而且我们取值时要把CSS属性转换成驼峰风格。为了方便,我现在再把它分离出来。


复制代码 代码如下:

var camelize = function(attr){
return attr.replace(/\-(\w)/g, function(all, letter){
return letter.toUpperCase();
});
}

接着我们单独解决IE的透明度问题,基本各大类库都是这样做的,可见这问题多么棘手,实在要感谢微软那帮天才:


复制代码 代码如下:

var getIEOpacity = function(el){
var filter;
if(!!window.XDomainRequest){
filter = el.style.filter.match(/progid:DXImageTransform.Microsoft.Alpha\(.?opacity=(.*).?\)/i);
}else{
filter = el.style.filter.match(/alpha\(opacity=(.*)\)/i);
}
if(filter){
var value = parseFloat(filter[1]);
if (!isNaN(value)) {
return value ? value / 100 : 0;
}
}
return 1;
}

这时我们的函数就变成这样:


复制代码 代码如下:

var getStyle = function(el, style){
if(!+"\v1"){
if(style == "opacity"){
return getIEOpacity(el)
}
return el.currentStyle[camelize(style)];
}else{
return document.defaultView.getComputedStyle(el, null).getPropertyValue(style)
}
}

接着下来float属性问题。IE这边是styleFloat,W3C是cssFloat。解决不是问题,不过每次都要转换太麻烦了,我参照Ext的实现把它们缓存起来。


复制代码 代码如下:

var propCache = [];
var propFloat = !+"\v1" ? 'styleFloat' : 'cssFloat';
var camelize = function(attr){
return attr.replace(/\-(\w)/g, function(all, letter){
return letter.toUpperCase();
});
}
var memorize = function(prop) { //意思为:check out form cache
return propCache[prop] || (propCache[prop] = prop == 'float' ? propFloat : camelize(prop));
}
var getIEOpacity = function(el){
//*****************略**********************
}
var getStyle = function (el, style){
if(!+"\v1"){
if(style == "opacity"){
return getIEOpacity(el)
}
return el.currentStyle[memorize(style)];
}else{
if(style == "float"){
style = propFloat;
}
return document.defaultView.getComputedStyle(el, null).getPropertyValue(style)
}
}

到最难的部分了——精确取得高度与宽度。如果用过JQuery的人都知道,John Resig是单独处理这两个属性。其实何止JQuery,其他库都为此头痛。问题的起因是如果没有内联样式或内部样式显式地设置这两个属性,我们在IE下是无法获取它们精确的值,或者获得的是百分比,空字符串,auto,inhert等让人无可奈何的东西。另,对于国人来说,px的地位是远远高于em。总总原因,让我们不能放弃这两个属性。为了取得这两个属性的精确值,我们就需要研究一下CSS继承问题了。我也已撰写相关博文《CSS的inhert与auto》来探讨这问题,没有看,请看完再回来,要不,根本是无法看下去的。
根据CSS的分类,width与height是属性于non-inherited property,由此可知,如果我们不为它设值,默认为auto。这个auto是个神奇的东东,如果是块状元素,它的宽就相当于100%,撑满父元素的内容区,当然这是在不考虑其padding,border与margin的情况下。如果是内联元素,由于不具备盒子模型,你给它设置宽与高是没有意义的,就算在火狐,返回的改过转换的精确值与你看到的情况完全不吻合,如果没有设置,直接返回auto。为了取得以px为单位的精确值,为了屏蔽块状元素与内联元素,我们需要转换思路,不过光盯着CSS属性转。这时,微软做了件好事,开发出offsetXX,clientXX与scrollXX三大家族,现在终于纳入W3C的标准。不过早在这之前,各浏览器已经跟风实现了。
在标准模式中,offsetWidth是包含padding,borderWidth与width,如果存在滚动条,它的offsetWidth也不会变,滚动条的宽度在各浏览器非常一致,都为17px,这时它就会把width减去17px,缺失的空间由滚动条补上。 offsetWidth 如果存在padding与padding,我们就要减去padding与padding 在怪癖模式下,offsetWidth等于width,而width是包含padding与borderWidth。 offsetHeight同理。 clientXX家族好理解,就是不包含borderWidth与滚动条。scrollXX家族不说了,在五大浏览器都不一致。因此,在标准模式中,要取得高与宽,首选clientXX家族,怪癖模式中,首先offsetXX家族。
这时不得不说一下怪癖模式了,别以为升到IE8,设置相应DocType就可以逃过一劫,只要你的网页太多地方不按标准写,IE也转为兼容模式中运作。这兼容模式是否等于怪癖模式就不得而已,因为IE8有多达五种渲染模式,IE5怪癖模式,IE7标准模式,IE8几乎标准模式,IE7兼容模式与支持HTML5的边缘模式。这么多模式,你以为光靠 document.compatMode == "CSS1Compat"能撑住吗?!撑不住的,幸好IE8在添加新模式的同时,又添加了一个 document.documentMode属性,真不知是喜还是悲了。因此判断是否运行于怪癖模式的代码为:
http://www.mangguo.org/x-ua-compatible-ie8-compatible-mode/


复制代码 代码如下:

var isQuirk = (document.documentMode) ? (document.documentMode==5) ? true :
false : ((document.compatMode=="CSS1Compat") ? false : true);

于是我们有如下伪码:


复制代码 代码如下:

var getWidth = function(el){
if(isQuirk){
return el.offsetWidth
}else{
return el.clientWidth - parseFloat(getStyle(el, "padding-left"))- parseFloat(getStyle(el, "padding-right"))
}
}

对比一下Ext的实现(只抠出核心部分):


复制代码 代码如下:

getWidth : function(contentWidth){
var me = this,
dom = me.dom,
w = MATH.max(dom.offsetWidth, dom.clientWidth) || 0;
w = !contentWidth ? w : w - me.getBorderWidth("lr") - me.getPadding("lr");
return w < 0 ? 0 : w;
},

非常危险的做法,比Prototype的实现还差劲,因此它就不得在其他部分进行纠正,这就是为什么它的UI体积如此庞大的缘故,从侧面也突现JQuery实现手法的高超。不过JQuery的实现了也是相当复杂,如果本元素无法取得精确值,就从上级元素着手,这个遍历消耗非常严重。其中还借用了Dean Edwards的一个伟大的hack:


复制代码 代码如下:

var convertPixelValue = function(el, value){
var style = el.style,left = style.left,rsLeft = el.runtimeStyle.left;
el.runtimeStyle.left = el.currentStyle.left;
style.left = value || 0;
var px = style.pixelLeft;
style.left = left;
el.runtimeStyle.left = rsLeft;
return px;
}
//此函数由Dean Edwards提供的,最早出处见下面链接
// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
//注意,第二参数必须是带单位的数值,如em', 'ex', 'cm', 'mm', 'in', 'pt', 'pc'
//百分比好像有点问题
//另,不要试图用于IE以外的浏览器
//用法:convertPixelValue($("drag4"),'10em')
var convertPixelValue = function(el, styleVal){
//保存原来的值到left与rsLeft
var style = el.style,left = style.left,rsLeft = el.runtimeStyle.left;
//下面这步是关键,
//把el.currentStyle.left代入到el.runtimeStyle.left中,激活hack
el.runtimeStyle.left = el.currentStyle.left;
//把非px单位的数值代入style.left中,如10em
style.left = styleVal || 0;
//一定要用style.pixelLeft去取,要不数值是不准确
//如果我们用style.pixelLeft会得160,用style.left会得到150px
//如果都已转换好,但style.left是不准确的。
var px = style.pixelLeft;
style.left = left;//还原数据
el.runtimeStyle.left = rsLeft;//还原数据
return px;
}

这个hack是用于将em、pc、pt、cm、in、ex等单位转换为px的,当然不包括百分比。
再回来看我的getWidth函数,主要问题是获取padding-left与padding-right的值。在标准浏览器,我们用getComputedStyle可以轻而易举地获取经过转换的精确值,单位为px。在IE中,如果你给它的值为2em,它就返回2em,很懒。在《CSS的inherit与auto》一文,我也指出了,padding为non-inherited property,这就不用处理inhert这个无厘头的值;在auto列表中,padding也不在列,减少了处理auto这个模糊值的风险;加之是可度量单位,各浏览器都很厚道地设置默认值为0px,换言之,我们需要的做的事是,当值的单位不为px,我们把它转换为px。我们把Dean Edwards的hack整合到我们的主函数getStyle中即可。如果padding-left是百分比,我们就取其父元素的width乘以百分比即可。总而言之,遍历的层次与数算的次数都被压缩最少。从这方面说,我在这方面的处理比JQuery优胜得多(JQuery连border,margin都列入计算范围,而border与margin在IE中存在模糊值,这就逼使JQuery动不动就往上计算父元素,换言之,需要重复计算其父元素的属性五次;我最多为三次,怪异模式下只需一次)。有时在IE中取得的值,比拥有getComputedStyle的火狐还精确(不过,好像精确过头,自己用toFixed调整精确度)。


复制代码 代码如下:

var getStyle = function (el, style){
if(!+"\v1"){
if(style == "opacity"){
return getIEOpacity(el)
}
var value = el.currentStyle[memorize(style)];
if (/^(height|width)$/.test(style)){
var values = (style == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0;
if(isQuirk){
return el[camelize("offset-"+style)]
}else{
var client = parseFloat(el[camelize("client-"+style)]),
paddingA = parseFloat(getStyle(el, "padding-"+ values[0])),
paddingB = parseFloat(getStyle(el, "padding-"+ values[1]));
return (client - paddingA - paddingB)+"px";
}
}
if(!/^\d+px$/.test(value)){
//转换可度量的值
if(/(em|pt|mm|cm|pc|in|ex|rem|vw|vh|vm|ch|gr)$/.test(value)){
return convertPixelValue(el,value);
}
//转换百分比
if(/%/.test(value)){
return parseFloat(getStyle(el.parentNode,"width")) * parseFloat(value) /100 + "px"
}
}
return value;//如 0px
}else{
if(style == "float"){
style = propFloat;
}
return document.defaultView.getComputedStyle(el, null).getPropertyValue(style)
}
}

说多无谓,我们测试一下吧。
<div id ="text" style="width:3in;height:180px;background:#8080c0;padding:2%;">父元素
<div id="text2" style="width:78%;height:4em;padding:1%;background:red;border:1px solid red">子元素</div>
</div>
window.onload = function(){
alert(getStyle(_("text"),"width"))
alert(getStyle(_("text2"),'width'))
alert(getStyle(_("text2"),'padding-left'))
};

var isQuirk = (document.documentMode) ? (document.documentMode==5) ? true : false : ((document.compatMode=="CSS1Compat") ? false : true);
var propCache = [];
var propFloat = !+"\v1" ? 'styleFloat' : 'cssFloat';
var camelize = function(attr){
return attr.replace(/\-(\w)/g, function(all, letter){
return letter.toUpperCase();
});
}
var memorize = function(prop) {
return propCache[prop] || (propCache[prop] = prop == 'float' ? propFloat : camelize(prop));
}
var getIEOpacity = function(el){
var filter;
if(!!window.XDomainRequest){
filter = el.style.filter.match(/progid:DXImageTransform.Microsoft.Alpha\(.?opacity=(.*).?\)/i);
}else{
filter = el.style.filter.match(/alpha\(opacity=(.*)\)/i);
}
if(filter){
var value = parseFloat(filter[1]);
if (!isNaN(value)) {
return value ? value / 100 : 0;
}
}
return 1;
}
var convertPixelValue = function(el, value){
var style = el.style,left = style.left,rsLeft = el.runtimeStyle.left;
el.runtimeStyle.left = el.currentStyle.left;
style.left = value || 0;
var px = style.pixelLeft;
style.left = left;//还原数据
el.runtimeStyle.left = rsLeft;//还原数据
return px;
}
var getStyle = function (el, style){
if(!+"\v1"){
if(style == "opacity"){
return getIEOpacity(el)
}
var value = el.currentStyle[memorize(style)];
if (/^(height|width)$/.test(style)){
var values = (style == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0;
if(isQuirk){
return el[camelize("offset-"+style)]
}else{
var client = parseFloat(el[camelize("client-"+style)]),
paddingA = parseFloat(getStyle(el, "padding-"+ values[0])),
paddingB = parseFloat(getStyle(el, "padding-"+ values[1]));
return (client - paddingA - paddingB)+"px";
}
}
var unit = value.match(/^\d*\.?\d*\s*([\w%]+)$/)[1];
if(/^(em|pt|mm|cm|pc|in|ex|rem|vw|vh|vm|ch|gr)$/.test(unit)){
return convertPixelValue(el,value);
}
if(/%/.test(unit)){
return parseFloat(getStyle(el.parentNode,"width")) * parseFloat(value) /100 + "px"
}
return value;//如 0px
}else{
if(style == "float"){
style = propFloat;
}
return document.defaultView.getComputedStyle(el, null).getPropertyValue(style)
}
}
var _ = function(id){
return document.getElementById(id);
}
window.onload = function(){
alert(getStyle(_("text"),"width"))
alert(getStyle(_("text2"),'width'))
alert(getStyle(_("text2"),'padding-left'))
};

精确取值

父元素

子元素

[Ctrl+A 全选 注:如需引入外部Js需刷新才能执行]

发现在IE取得值太精确了,比火狐还准确,不过我也不打算在程序内部设置取精度的运算,因为我不确定现实中究竟会向上遍历多少次。在某一父元素的padding与width取精度,很可能会严重影响其最终值的精度。基本上,width与height与padding的取值就到此为止,我们再来看盒子模型的两个东西:margin与border。
margin的auto通常为0px,但在IE6下,当我们将其父元素的text-align设为center,它不但把文本居中,连块级元素本身也忧患中了,这是IE6居中布局的基石。很难说这是BUG,就像IE5的怪癖模式的盒子模型那样,很符合人通常的思维习惯,但是较难实现,加之W3C是偏袒被微软阴死的网景,于是网景的盒子模型成为标准了。IE6庞大的用户基础不容忽视,我们不能随便给0px了事。我也说了,auto有个对称性,因此好办,我们求出其父元素的width然后减于其offsetWidth再除以2就行了。因为offsetWidth是针对于offsertParent的,因此我们用时临时把其父元素的position设为relative,让子元素来得offsetWidth后,再将父元素的position还原。


复制代码 代码如下:

//转换margin的auto
if(/^(margin).+/.test(style) && value == "auto"){
var father = el.parentNode;
if(/MSIE 6/.test(navigator.userAgent) && getStyle(father,"text-align") == "center"){
var fatherWidth = parseFloat(getStyle(father,"width")),
_temp = getStyle(father,"position");
father.runtimeStyle.postion = "relative";
var offsetWidth = el.offsetWidth;
father.runtimeStyle.postion = _temp;
return (fatherWidth - offsetWidth)/2 + "px";
}
return "0px";
}

borderWidth的默认值为medium,即使它为0px,但如果我们显式地设置其宽为medium呢?!它就不为0px了。这个比较恶心,我们需要判定这值是我们自己加上的还是浏览器的默认值。不过我们发现如果是默认的,其border-XX-style为none。另,除了medium外,还存在两个模糊值thin与thick。它们在浏览器的精确值见下表:


















IE8 ,firefox等标准浏览器 IE4-7
thin 1px 2px
medium 3px 4px
thick 5px 6px


复制代码 代码如下:

//转换border的thin medium thick
if(/^(border).+(width)$/.test(style)){
var s = style.replace("width","style"),
b = {
thin:["1px","2px"],
medium:["3px","4px"],
thick:["5px","6px"]
};
if(value == "medium" && getStyle(el,s) == "none"){
return "0px";
}
return !!window.XDomainRequest ? b[value][0] : b[value][1];
}

再看top,left,right与bottom,想不到firefox,safari都会偷懒,会返回auto,只有opera老老实实地返回精确值。解决办法也很简单,因为微软一个好用的函数已被所有浏览器支持了。


复制代码 代码如下:

//转换top|left|right|bottom的auto
if(/(top|left|right|bottom)/.test(style) && value == "auto"){
return el.getBoundingClientRect()[style]
}

嗯,文章已经很长很长,剩下的部分下次再讲。

时间: 2009-12-30

javascript 精确获取样式属性(下)

复制代码 代码如下: var rgb2hex = function(rgb) { rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); return "#"+tohex(rgb[1])+tohex(rgb[2])+tohex(rgb[3]) } var tohex = function(x) { var hexDigits = ['0','1','2','3','4','5','6','7','8','9','A','B','C','

javascript 精确获取页面元素的位置

复制代码 代码如下: //取得元素x坐标 function pageX(elem) { return elem.offsetParent?(elem.offsetLeft+pageX(elem.offsetParent)):elem.offsetLeft; } //取得元素y坐标 function pageY(elem) { return elem.offsetParent?(elem.offsetTop+pageY(elem.offsetParent)):elem.offsetTop; } 貌

JavaScript中获取样式的原生方法小结

ps:是获取样式,不是设置样式.若没有给元素设置样式值,则返回浏览器给予的默认值.(论坛整理) 1.element.style:只能获取写在元素标签中的style属性里的样式值,无法获取到定义在<style></style>和通过<link href="css.css">加载进来的样式属性 复制代码 代码如下: var ele = document.getElementById('ele'); ele.style.color;    //获取颜色 2

javascript获取作用在元素上面的样式属性代码

复制代码 代码如下: window.onload = function(){ var oDIv = document.getElementById('progressBox'); var sytleElemt = window.getComputedStyle(oDIv); for(var i=0;i<sytleElemt.length;i++){ if( typeof sytleElemt[sytleElemt[i]] != 'undefined' && sytleElemt[sy

原生javascript获取元素样式属性值的方法

所以, 我们得利用IE的currentStyle和W3C的getPropertyValue获取. elem.style.attr获取样式的方法就不说了. 先来看currentStyle方法, 此对象ie专属, 代表了在全局样式表.内嵌样式和 HTML 标签属性中指定的对象格式和样式. IE下通过它, 就可以获取元素的Css属性值. 而针对其他标准浏览器, W3C也提供了一个方法getPropertyValue, 此方法, 稍有点复杂, 首先要通过document.defaultView.getC

Mootools 1.2教程 设置和获取样式表属性

欢迎开始这一系列的教程的第七讲.今天,我们来看一下如何通过MooTools 1.2和我们以前几讲中的内容来操作样式,这将给你在UI上带来很大的控制权.处理样式非常简单,不过今天我们要做一些调整.例如,我们要介绍键值对(key-value pair)对象.我们也会讲到在domready之外来传递变量,就像我们在关于函数的那一讲中学到的一样.从这里开始,我们会开始慢慢提高难度,介绍一些必要的编程概念.如果你是JavaScript新手或者第一次开始学MooTools,请确保你在明白了前面的教程,你可以

jQuery获取样式中的背景颜色属性值/颜色值

天使用jQuery获取样式中的background-color的值时发现在获取到的颜色值在IE中与Chrome.Firefox显示的格式不一样,IE中是以HEX格式显示#ffff00,而Chrome.Firefox中则是以GRB格式显示rgb(255,0,0),由于需要将颜色值存储到数据库中,所以想让颜色值的格式统一下(其实不统一也是可以存的).搜索了一下,从国外的一个网站上得到一段代码 复制代码 代码如下: $.fn.getHexBackgroundColor = function() { v

javaScript 读取和设置文档元素的样式属性

首先我们先说一下样式表属性 1. 内联样式即元素style属性里面设置的,级别最高 2. 页面样式表定义即页面<style></style>里面定义的,级别次之 3.外部链接样式表文件 JavaScript获取和设置文档元素的css属性: 1.获取元素Style属性里面设置的样式属性, document.getElementById(id).style.height; 有,则返回属性值:没有则返回空 IE和火狐皆然,只是有的属性值返回可能不一样,比如像颜色火狐返回rgb,而IE是返

javascript实现获取指定精度的上传文件的大小简单实例

js实现获取指定精度的上传文件的大小,主要采用html和JavaScript,用浏览器运行下述代码,按照操作:选择文件->获得文件大小的顺序,即可. 源码: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>获得文件大小</title> </head> <body> <f