javascript 精確擷取樣式屬性(上)_javascript技巧

來源:互聯網
上載者:User
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下是無法擷取它們精確的值,或者獲得的是百分比,Null 字元串,auto,inhert等讓人無可奈何的東西。另,對於國人來說,px的地位是遠遠高於em。總總原因,讓我們不能放棄這兩個屬性。為了取得這兩個屬性的精確值,我們就需要研究一下CSS繼承問題了。我也已撰寫相關博文《CSS的inhert與auto》來探討這問題,沒有看,請看完再回來,要不,根本是無法看下去的。
根據CSS的分類,width與height是屬性於non-inherited property,由此可知,如果我們不為它設值,預設為auto。這個auto是個神奇的東東,如果是塊狀元素,它的寬就相當於100%,撐滿父元素的內容區,當然這是在不考慮其padding,border與margin的情況下。如果是內嵌元素,由於不具備盒子模型,你給它設定寬與高是沒有意義的,就算在Firefox,返回的改過轉換的精確值與你看到的情況完全不吻合,如果沒有設定,直接返回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的Firefox還精確(不過,好像精確過頭,自己用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'))
};
<!doctype html> <html dir="ltr" lang="zh-CN"> <head> <meta charset="utf-8"/> <meta http-equiv="X-UA-Compatible" content="IE=8"> <style type="text/css"> </style> <script> 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')) }; </script> <title>精確取值</title> </head> <body> <div id ="text" >父元素 <div id="text2" >子項目</div> </div> </body> </html>
[Ctrl+A 全選 注:如需引入外部Js需重新整理才能執行]

發現在IE取得值太精確了,比Firefox還準確,不過我也不打算在程式內部設定取精度的運算,因為我不確定現實中究竟會向上遍曆多少次。在某一父元素的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]
}

嗯,文章已經很長很長,剩下的部分下次再講。
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.