最近一直在研讀 jQuery 原始碼,初看原始碼一頭霧水毫無頭緒,真正靜下心來細看寫的真是精妙,讓你感嘆程式碼之美。
其結構明晰,高內聚、低耦合,兼具優秀的效能與便利的擴充套件性,在瀏覽器的相容性(功能缺陷、漸進增強)優雅的處理能力以及 Ajax 等方面周到而強大的定製功能無不令人驚歎。
另外,閱讀原始碼讓我接觸到了大量底層的知識。對原生JS 、框架設計、程式碼優化有了全新的認識,接下來將會寫一系列關於 jQuery 解析的文章。
我在 github 上關於 jQuery 原始碼的全文註解,感興趣的可以圍觀一下。jQuery v1.10.2 原始碼註解 。
言歸正傳,本文講的是原生 JS 獲取、設定元素最終樣式的方法。可能平時框架使用習慣了,以 jQuery 為例,使用 .css() 介面就能便捷的滿足我們的要求。再看看今天要講的 window.getComputedStyle ,就像剛接觸 JS 的時候,看到 document.getElementById 一樣,名字都這麼長,頓生怯意。不過莫慌,我覺得如果我們不是隻想做一個混飯吃的前端,那麼越是底層的東西,如果能夠吃透它,越是能讓人進步。
getComputedStyle 與 getPropertyValue
getComputedStyle 為何物呢,DOM 中 getComputedStyle 方法可用來獲取元素中所有可用的css屬性列表,以陣列形式返回,並且是隻讀的。IE678 中則用 currentStyle 代替 。
假設我們頁面上存在一個 id 為 id 的元素,那麼使用 getComputedStyle 獲取元素樣式就如下圖所示:
1 2 3 4 |
// 語法: // 在舊版本之前,第二個引數“偽類”是必需的,現代瀏覽器已經不是必需引數了 // 如果不是偽類,設定為null, window.getComputedStyle("元素", "偽類"); |
嘗試一下之後可以看到,window.getComputedStyle 獲取的是所有的樣式,如果我們只是要獲取單一樣式,該怎麼做呢。這個時候就要介紹另一個方法 — getPropertyValue 。
用法也很簡單:
1 2 3 |
// 語法: // 使用 getPropertyValue 來指定獲取的屬性 window.getComputedStyle("元素", "偽類").getPropertyValue(style); |
IE 下的 currentStyle 與 getAttribute
說完常規瀏覽器,再來談談老朋友 IE ,與 getComputedStyle 對應,在 IE 中有自己特有的 currentStyle 屬性,與 getPropertyValue 對應,IE 中使用 getAttribute 。
和 getComputedStyle 方法不同的是,currentStyle 要獲得屬性名的話必須採用駝峰式的寫法。也就是如果我需要獲取 font-size 屬性,那麼傳入的引數應該是 fontSize。因此在IE 中要獲得單個屬性的值,就必須將屬性名轉為駝峰形式。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// IE 下語法: // IE 下將 CSS 命名轉換為駝峰表示法 // font-size --> fontSize // 利用正則處理一下就可以了 function camelize(attr) { // /-(w)/g 正則內的 (w) 是一個捕獲,捕獲的內容對應後面 function 的 letter // 意思是將 匹配到的 -x 結構的 x 轉換為大寫的 X (x 這裡代表任意字母) return attr.replace(/-(w)/g, function(all, letter) { return letter.toUpperCase(); }); } // 使用 currentStyle.getAttribute 獲取元素 element 的 style 屬性樣式 element.currentStyle.getAttribute(camelize(style)); |
style 與 getComputedStyle
必須要提出的是,我們使用 element.style 也可以獲取元素的CSS樣式宣告物件,但是其與 getComputedStyle 方法還是有一些差異的。
首先,element.style 是可讀可寫的,而 getComputedStyle 為只讀。
其次,element.style 只可以獲取 style 樣式上的屬性值,而無法得到所有的 CSS 樣式值,什麼意思呢?回顧一下 CSS 基礎,CSS 樣式表的表現有三種方式,
- 內嵌樣式(inline Style) :是寫在 HTML 標籤裡面的,內嵌樣式只對該標籤有效。
- 內部樣式(internal Style Sheet):是寫在 HTML 的 標籤裡面的,內部樣式只對所在的網頁有效。
- 外部樣式表(External Style Sheet):如果很多網頁需要用到同樣的樣式(Styles),將樣式(Styles)寫在一個以 .CSS 為字尾的 CSS 檔案裡,然後在每個需要用到這些樣式(Styles)的網頁裡引用這個 CSS 檔案。
而 element.style 只能獲取被這些樣式表定義了的樣式,而 getComputedStyle 能獲取到所有樣式的值(在不同瀏覽器結果不一樣,chrome 中是 264,在 Firefox 中是238),不管是否定義在樣式表中,譬如:
1 2 3 4 5 6 7 8 9 10 11 |
<style> #id{ width : 100px; float:left; } </style> var elem = document.getElementById('id'); elem.style.length // 2 window.getComputedStyle(elem, null).length // 264 |
getComputedStyle 與 defaultView
window.getComputedStyle 還有另一種寫法,就是 document.defaultView.getComputedStyle 。
兩者的用法完全一樣,在 jQuery v1.10.2 中,使用的就是 window.getComputedStyle 。如下
也有特例,檢視 stackoverflow ,上面提及到在 Firefox 3.6 ,不使用 document.defaultView.getComputedStyle 會出錯。不過畢竟 FF3.6 已經隨歷史遠去,現在可以放心的使用 window.getComputedStyle。
用一張圖總結一下:
原生JS實現CSS樣式的get與set
說了這麼多,接下來將用原生 JS 實現一個小元件,實現 CSS 的 get 與 set,相容所有瀏覽器。
完整的元件程式碼在我的 github 上,戳我直接看程式碼。
getStyle(elem, style)
對於 CSS 的 set ,對於支援 window.getComputedStyle 的瀏覽器而言十分簡單,只需要直接呼叫。
1 2 3 4 5 6 |
getStyle: function(elem, style) { // 主流瀏覽器 if (win.getComputedStyle) { return win.getComputedStyle(elem, null).getPropertyValue(style); } } |
反之,如果是 IE 瀏覽器,則有一些坑。
opacity 透明度的設定
在早期的 IE 中要設定透明度的話,有兩個方法:
- alpha(opacity=0.5)
- filter:progid:DXImageTransform.Microsoft.gradient( GradientType= 0 , startColorstr = ‘#ccccc’, endColorstr = ‘#ddddd’ );
因此在 IE 環境下,我們需要針對透明度做一些處理。先寫一個 IE 下獲取透明度的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// IE 下獲取透明度 function getIEOpacity(elem) { var filter = null; // 早期的 IE 中要設定透明度有兩個方法: // 1、alpha(opacity=0) // 2、filter:progid:DXImageTransform.Microsoft.gradient( GradientType= 0 , startColorstr = ‘#ccccc’, endColorstr = ‘#ddddd’ ); // 利用正則匹配 filter = elem.style.filter.match(/progid:DXImageTransform.Microsoft.Alpha(.?opacity=(.*).?)/i) || elem.style.filter.match(/alpha(opacity=(.*))/i); if (filter) { var value = parseFloat(filter); if (!isNaN(value)) { // 轉化為標準結果 return value ? value / 100 : 0; } } // 透明度的值預設返回 1 return 1; } |
float 樣式的獲取
float 屬性是比較重要的一個屬性,但是由於 float 是 ECMAScript 的一個保留字。(ECMAScript保留字有哪些?戳這裡)
所以在各瀏覽器中都會有代替的寫法,比如說在標準瀏覽器中為 cssFloat,而在 IE678 中為 styleFloat 。經測試,在標準瀏覽器中直接使用 getPropertyValue(“float”) 也可以獲取到 float 的值。而 IE678 則不行,所以針對 float ,也需要一個 HACK。
width | height 樣式的獲取
然後是元素的高寬,對於一個沒有設定高寬的元素而言,在 IE678 下使用 getPropertyValue(“width|height”) 得到的是 auto 。而標準瀏覽器會直接返回它的 px 值,當然我們希望在 IE 下也返回 px 值。
這裡的 HACK 方法是使用 element.getBoundingClientRect() 方法。
element.getBoundingClientRect() — 可以獲得元素四個點相對於文件檢視左上角的值 top、left、bottom、right ,通過計算就可以容易地獲得準確的元素大小。
獲取樣式的駝峰表示法
上文已經提及了,在IE下使用 currentStyle 要獲得屬性名的話必須採用駝峰式的寫法。
OK,需要 HACK 的點已經提完了。那麼在 IE 下,獲取樣式的寫法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
getStyle: function(elem, style) { // 主流瀏覽器 if (win.getComputedStyle) { ... // 不支援 getComputedStyle } else { // IE 下獲取透明度 if (style == "opacity") { getIEOpacity(elem); // IE687 下獲取浮動使用 styleFloat } else if (style == "float") { return elem.currentStyle.getAttribute("styleFloat"); // 取高寬使用 getBoundingClientRect } else if ((style == "width" || style == "height") & (elem.currentStyle[style] == "auto")) { var clientRect = elem.getBoundingClientRect(); return (style == "width" ? clientRect.right - clientRect.left : clientRect.bottom - clientRect.top) + "px"; } // 其他樣式,無需特殊處理 return elem.currentStyle.getAttribute(camelize(style)); } } |
setStyle(elem, style, value)
說完 get ,再說說 setStyle ,相較於getStyle ,setStyle 則便捷很多,因為不管是標準瀏覽器還是 IE ,都可以使用 element.style.cssText 對元素進行樣式的設定。
cssText — 一種設定 CSS 樣式的方法,但是它是一個銷燬原樣式並重建的過程,這種銷燬和重建,會增加瀏覽器的開銷。而且在 IE 中,如果 cssText(假如不為空),最後一個分號會被刪掉,所以我們需要在其中新增的屬性前加上一個 ”;” 。
只是在 IE 下的 opacity 需要額外的進行處理。明瞭易懂,直接貼程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// 設定樣式 setStyle: function(elem, style, value) { // 如果是設定 opacity ,需要特殊處理 if (style == "opacity") { //IE7 bug:filter 濾鏡要求 hasLayout=true 方可執行(否則沒有效果) if (!elem.currentStyle || !elem.currentStyle.hasLayout) { // 設定 hasLayout=true 的一種方法 elem.style.zoom = 1; } // IE678 設定透明度叫 filter ,不是 opacity style = "filter"; // !!轉換為 boolean 型別進行判斷 if (!!window.XDomainRequest) { value = "progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=" + value * 100 + ")"; } else { value = "alpha(opacity=" + value * 100 + ")" } } // 通用方法 elem.style.cssText += ';' + (style + ":" + value); } |
到這裡,原生 JS 實現的 getStyle 與 setStyle 就實現了,完整的程式碼可以戳這裡檢視。可以看到,一個簡單介面的背後,都是有涉及了很多方面東西。雖然瀏覽器相容性是一個坑,但是爬坑的過程卻是我們沉澱自己的最好時機。
jQuery 這樣的框架可以幫助我們走的更快,但是毫無疑問,去弄清底層實現,掌握原生 JS 的寫法,可以讓我們走得更遠。
原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。
如果本文對你有幫助,請點下推薦,寫文章不容易。
最後,本文元件示例的程式碼貼在 我的github 上。
我在 github 上關於 jQuery 原始碼的全文註解,感興趣的可以圍觀一下。jQuery v1.10.2 原始碼註解 。