1. 程式人生 > >#靈感筆記#關於增強IE對CSS選擇符的支援

#靈感筆記#關於增強IE對CSS選擇符的支援

雖然在官方規範之中,HTML有版本,CSS有級別,但實踐當中無所謂版本級別,關鍵是看瀏覽器是否支援。

如IE6不支援CSS子元素選擇符,就非常令人沮喪。
雖有其它替代方案,比如給HTML元素都加上類名,但那樣程式碼量會增加不少,而且不能很好的利用HTML元素語義。

目前有一個第三方js庫 Selectivizr(http://selectivizr.com/),當前版本v1.0.2,可增強IE對很多CSS3的偽類選擇符及屬性選擇符的支援效果,需要配合一些“第四方”js庫(如jQuery)來使用。
其原理大概如此:分析當前頁面載入的外部樣式表URL,通過ajax重新讀取這些樣式表,將其中IE6不支援的選擇符替換成特定的類名,同時給其對應的HTML元素也加上此類名,最後將修改過的樣式表附加到文件。
不足之處:


1.只能處理外部樣式表,頁面內嵌的<style></style>元素未做處理。
2.需要通過ajax重新載入一次樣式表,不必要的資源消耗。
3.對於複雜的選擇符,只用一個簡單的類名代替,原有的CSS優先順序權重關係被嚴重破壞。
4.修改過的樣式表破壞了原有的順序,也有可能影響到優先順序。
5.關鍵是它目前還不支援在IE6中使用子元素選擇符“E>F”

下面討論如何來進一步增強IE的選擇符支援
IE提供了一些DOM API來訪問CSS規則(CSS Rules),程式碼形式大致如下:

document.styleSheets // 獲取當前文件的全部樣式表,包括外部和頁內的
document.styleSheets[0].rules // 獲取某個樣式表的全部規則
document.styleSheets[0].rules[0].selectorText // 獲取樣式規則的選擇符文字(MS官方DHTML手冊說明為可讀寫,但實際上寫操作未實現)
document.styleSheets[0].rules[0].style.cssText // 獲取樣式規則的內容
document.styleSheets[0].removeRule([index]); // 移除一條樣式規則
document.styleSheets[0].addRule(selector, cssText, [index]); // 增加一條樣式規則
利用這些API,前面提到的Selectivizr不足點1,2,好像都可以避免,但它為何沒有這麼做呢?
可能原因是:對於IE不支援的選擇符,rule.selectorText會返回“UNKNOW”或者包含“unknow”的字串。
所以無法用此API得知原始的選擇符文字。

但是IE又有另一個特點:對於它不支援的樣式規則內容,基本上會保持原始文字。例如

<style type="text/css">
:my-pseudo-class {
	foo: bar;
}
[attr] {
	selector: [attr];
}
</style>
<script type="text/javascript">
alert(document.styleSheets[0].rules.length);
alert(document.styleSheets[0].rules[0].selectorText); // :unknow
alert(document.styleSheets[0].rules[0].style.cssText); // foo: bar
alert(document.styleSheets[0].rules[1].selectorText); // UNKNOW
alert(document.styleSheets[0].rules[1].style.cssText); // selector: [attr]
</script>

所以在新方案中,可以採取一個折中的辦法來解決前面的1、2點不足:
首先獲取選擇符:
約定一種格式(樣式屬性名),利用css hack來記錄選擇符,比如:“selector: body>nav”;
遍歷rule,通過rule.style.cssText提取規則內容,並解析selector(如果不包含selector屬性則跳過)。

然後分析選擇符並建立新類名進行選擇符替換:
對於有“包含選擇符”(E F)關係的複雜選擇符,按照包含的層次關係,將其劃分為多個段;
對每個段依次進行分析,如果IE6完全支援,則不作處理;
如果某個段包含IE6不支援的偽類(:pseudo-class)、屬性([attr])、關係(E>F,E+F,E~F)、多類名(.cls1.cls2)等選擇符,則建立一個新的類名對應此段,同時給相應的HTML元素加上新類名(可利用“第四方”庫來完成);
各段處理完成之後,按照其原有的順序重新組合成一個新的“包含選擇符”,通過removeRule()和addRule()來替換原有規則。
對於無“包含”關係的選擇符,可以直接採取建立新類名替換的辦法處理。
至此,前面的3、4、5點不足也基本解決。

新方案優點:

1.支援頁內樣式和外部樣式。
2.儘量減少了對複雜選擇符替換造成的優先順序權重影響。
3.可擴充套件性好,可以支援未來可能出現的新型選擇符。

新方案缺點:

1.使用了css hack,樣式程式碼增加了體積;
2.需要預先判斷哪些selector是IE不支援的,維護可能會帶來一點小麻煩(需要hack的selector必須寫兩遍),不過也許可以通過一些自動化指令碼來處理。

需要進一步優化的方向:
文件內容動態變化之後,需要重新審查文件元素與新類名的關聯關係。
需要針對IE6~8不同版本進行優化。

擴充套件思考:
調研的時候考慮過,其它非IE瀏覽器是否也可以使用類似的hack辦法來實現一些瀏覽器本身不支援的“選擇符”,不過暫時未發現通過API能方便讀取hack寫法的方法;唯一可以利用的倒是font-family屬性——可以包含各種字元,易於讀取,也能被瀏覽器選擇性忽略的屬性。

PS:IE選擇符支援情況簡單測試法。

<script type="text/javascript">
function isSupportSelector(selector) {
	var head = document.getElementsByTagName('head')[0];
	var style = document.createElement('style');
	head.appendChild(style);
	style.styleSheet.cssText = selector+'{}';
	// alert(style.styleSheet.cssText);
	var support = !/unknow/i.test(style.styleSheet.cssText);
	head.removeChild(style);
	return support;
}
alert(isSupportSelector('nav a'));
alert(isSupportSelector('nav:first-child'));
alert(isSupportSelector('nav:first-child a'));
alert(isSupportSelector('.cls1.cls2')); // 多類名檢測不到,待改進
</script>

PS:關於selector的解析,可以參考jQuery/Sizzle原始碼中的chunker正則表示式。

PS:暫只記錄想法,無完整程式碼實現。