JQuery,mootools,Ext等類(lèi)庫(kù)在這部分實(shí)現(xiàn)得非常艱辛,盤(pán)根錯(cuò)節(jié)地動(dòng)用一大堆方法,因此想把這部分摳出來(lái)難度很大。深入研究它們的實(shí)現(xiàn)后,根據(jù)我積累的CSS知識(shí),終于做出一個(gè)非常簡(jiǎn)煉的版本出來(lái)。它相當(dāng)于JQuery.cssCur吧,不過(guò)或許功能還豐富一些,按飲食業(yè)話說(shuō)叫“加量不加價(jià)”,我的可能還應(yīng)叫“加量還減價(jià)”……版本還處于Beta階段,由于只個(gè)工具函數(shù)就不弄成類(lèi)了。
代碼如下:
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)
}
}
這是函數(shù)的最原始狀態(tài),由于used value是W3C那邊的人搞出來(lái)的,因此document.defaultView.getComputedStyle 基本連動(dòng)也不動(dòng)就解決百分之99的問(wèn)題。IE那邊的復(fù)雜了,雖然微軟搞了style,currentStyle與runtimeStyle,但始終都沒(méi)有一個(gè)與getComputedStyle相近的實(shí)現(xiàn),最相近的是currentStyle,它只能取到內(nèi)部樣式,而且我們?nèi)≈禃r(shí)要把CSS屬性轉(zhuǎn)換成駝峰風(fēng)格。為了方便,我現(xiàn)在再把它分離出來(lái)。
代碼如下:
var camelize = function(attr){
return attr.replace(/\-(\w)/g, function(all, letter){
return letter.toUpperCase();
});
}
接著我們單獨(dú)解決IE的透明度問(wèn)題,基本各大類(lèi)庫(kù)都是這樣做的,可見(jiàn)這問(wèn)題多么棘手,實(shí)在要感謝微軟那幫天才:
代碼如下:
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;
}
這時(shí)我們的函數(shù)就變成這樣:
代碼如下:
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)
}
}
接著下來(lái)float屬性問(wèn)題。IE這邊是styleFloat,W3C是cssFloat。解決不是問(wèn)題,不過(guò)每次都要轉(zhuǎn)換太麻煩了,我參照Ext的實(shí)現(xiàn)把它們緩存起來(lái)。
代碼如下:
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)
}
}
到最難的部分了——精確取得高度與寬度。如果用過(guò)JQuery的人都知道,John Resig是單獨(dú)處理這兩個(gè)屬性。其實(shí)何止JQuery,其他庫(kù)都為此頭痛。問(wèn)題的起因是如果沒(méi)有內(nèi)聯(lián)樣式或內(nèi)部樣式顯式地設(shè)置這兩個(gè)屬性,我們?cè)贗E下是無(wú)法獲取它們精確的值,或者獲得的是百分比,空字符串,auto,inhert等讓人無(wú)可奈何的東西。另,對(duì)于國(guó)人來(lái)說(shuō),px的地位是遠(yuǎn)遠(yuǎn)高于em??偪傇颍屛覀儾荒芊艞夁@兩個(gè)屬性。為了取得這兩個(gè)屬性的精確值,我們就需要研究一下CSS繼承問(wèn)題了。我也已撰寫(xiě)相關(guān)博文《CSS的inhert與auto》來(lái)探討這問(wèn)題,沒(méi)有看,請(qǐng)看完再回來(lái),要不,根本是無(wú)法看下去的。
根據(jù)CSS的分類(lèi),width與height是屬性于non-inherited property,由此可知,如果我們不為它設(shè)值,默認(rèn)為auto。這個(gè)auto是個(gè)神奇的東東,如果是塊狀元素,它的寬就相當(dāng)于100%,撐滿(mǎn)父元素的內(nèi)容區(qū),當(dāng)然這是在不考慮其padding,border與margin的情況下。如果是內(nèi)聯(lián)元素,由于不具備盒子模型,你給它設(shè)置寬與高是沒(méi)有意義的,就算在火狐,返回的改過(guò)轉(zhuǎn)換的精確值與你看到的情況完全不吻合,如果沒(méi)有設(shè)置,直接返回auto。為了取得以px為單位的精確值,為了屏蔽塊狀元素與內(nèi)聯(lián)元素,我們需要轉(zhuǎn)換思路,不過(guò)光盯著CSS屬性轉(zhuǎn)。這時(shí),微軟做了件好事,開(kāi)發(fā)出offsetXX,clientXX與scrollXX三大家族,現(xiàn)在終于納入W3C的標(biāo)準(zhǔn)。不過(guò)早在這之前,各瀏覽器已經(jīng)跟風(fēng)實(shí)現(xiàn)了。
在標(biāo)準(zhǔn)模式中,offsetWidth是包含padding,borderWidth與width,如果存在滾動(dòng)條,它的offsetWidth也不會(huì)變,滾動(dòng)條的寬度在各瀏覽器非常一致,都為17px,這時(shí)它就會(huì)把width減去17px,缺失的空間由滾動(dòng)條補(bǔ)上。 offsetWidth 如果存在padding與padding,我們就要減去padding與padding 在怪癖模式下,offsetWidth等于width,而width是包含padding與borderWidth。 offsetHeight同理。 clientXX家族好理解,就是不包含borderWidth與滾動(dòng)條。scrollXX家族不說(shuō)了,在五大瀏覽器都不一致。因此,在標(biāo)準(zhǔn)模式中,要取得高與寬,首選clientXX家族,怪癖模式中,首先offsetXX家族。
這時(shí)不得不說(shuō)一下怪癖模式了,別以為升到IE8,設(shè)置相應(yīng)DocType就可以逃過(guò)一劫,只要你的網(wǎng)頁(yè)太多地方不按標(biāo)準(zhǔn)寫(xiě),IE也轉(zhuǎn)為兼容模式中運(yùn)作。這兼容模式是否等于怪癖模式就不得而已,因?yàn)镮E8有多達(dá)五種渲染模式,IE5怪癖模式,IE7標(biāo)準(zhǔn)模式,IE8幾乎標(biāo)準(zhǔn)模式,IE7兼容模式與支持HTML5的邊緣模式。這么多模式,你以為光靠 document.compatMode == "CSS1Compat"能撐住嗎?!撐不住的,幸好IE8在添加新模式的同時(shí),又添加了一個(gè) document.documentMode屬性,真不知是喜還是悲了。因此判斷是否運(yùn)行于怪癖模式的代碼為:
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"))
}
}
對(duì)比一下Ext的實(shí)現(xiàn)(只摳出核心部分):
代碼如下:
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;
},
非常危險(xiǎn)的做法,比Prototype的實(shí)現(xiàn)還差勁,因此它就不得在其他部分進(jìn)行糾正,這就是為什么它的UI體積如此龐大的緣故,從側(cè)面也突現(xiàn)JQuery實(shí)現(xiàn)手法的高超。不過(guò)JQuery的實(shí)現(xiàn)了也是相當(dāng)復(fù)雜,如果本元素?zé)o法取得精確值,就從上級(jí)元素著手,這個(gè)遍歷消耗非常嚴(yán)重。其中還借用了Dean Edwards的一個(gè)偉大的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;
}
//此函數(shù)由Dean Edwards提供的,最早出處見(jiàn)下面鏈接
// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
//注意,第二參數(shù)必須是帶單位的數(shù)值,如em', 'ex', 'cm', 'mm', 'in', 'pt', 'pc'
//百分比好像有點(diǎn)問(wèn)題
//另,不要試圖用于IE以外的瀏覽器
//用法:convertPixelValue($("drag4"),'10em')
var convertPixelValue = function(el, styleVal){
//保存原來(lái)的值到left與rsLeft
var style = el.style,left = style.left,rsLeft = el.runtimeStyle.left;
//下面這步是關(guān)鍵,
//把el.currentStyle.left代入到el.runtimeStyle.left中,激活hack
el.runtimeStyle.left = el.currentStyle.left;
//把非px單位的數(shù)值代入style.left中,如10em
style.left = styleVal || 0;
//一定要用style.pixelLeft去取,要不數(shù)值是不準(zhǔn)確
//如果我們用style.pixelLeft會(huì)得160,用style.left會(huì)得到150px
//如果都已轉(zhuǎn)換好,但style.left是不準(zhǔn)確的。
var px = style.pixelLeft;
style.left = left;//還原數(shù)據(jù)
el.runtimeStyle.left = rsLeft;//還原數(shù)據(jù)
return px;
}
這個(gè)hack是用于將em、pc、pt、cm、in、ex等單位轉(zhuǎn)換為px的,當(dāng)然不包括百分比。
再回來(lái)看我的getWidth函數(shù),主要問(wèn)題是獲取padding-left與padding-right的值。在標(biāo)準(zhǔn)瀏覽器,我們用getComputedStyle可以輕而易舉地獲取經(jīng)過(guò)轉(zhuǎn)換的精確值,單位為px。在IE中,如果你給它的值為2em,它就返回2em,很懶。在《CSS的inherit與auto》一文,我也指出了,padding為non-inherited property,這就不用處理inhert這個(gè)無(wú)厘頭的值;在auto列表中,padding也不在列,減少了處理auto這個(gè)模糊值的風(fēng)險(xiǎn);加之是可度量單位,各瀏覽器都很厚道地設(shè)置默認(rèn)值為0px,換言之,我們需要的做的事是,當(dāng)值的單位不為px,我們把它轉(zhuǎn)換為px。我們把Dean Edwards的hack整合到我們的主函數(shù)getStyle中即可。如果padding-left是百分比,我們就取其父元素的width乘以百分比即可??偠灾闅v的層次與數(shù)算的次數(shù)都被壓縮最少。從這方面說(shuō),我在這方面的處理比JQuery優(yōu)勝得多(JQuery連border,margin都列入計(jì)算范圍,而border與margin在IE中存在模糊值,這就逼使JQuery動(dòng)不動(dòng)就往上計(jì)算父元素,換言之,需要重復(fù)計(jì)算其父元素的屬性五次;我最多為三次,怪異模式下只需一次)。有時(shí)在IE中取得的值,比擁有g(shù)etComputedStyle的火狐還精確(不過(guò),好像精確過(guò)頭,自己用toFixed調(diào)整精確度)。
代碼如下:
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)){
//轉(zhuǎn)換可度量的值
if(/(em|pt|mm|cm|pc|in|ex|rem|vw|vh|vm|ch|gr)$/.test(value)){
return convertPixelValue(el,value);
}
//轉(zhuǎn)換百分比
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)
}
}
說(shuō)多無(wú)謂,我們測(cè)試一下吧。
父元素
子元素
window.onload = function(){
alert(getStyle(_("text"),"width"))
alert(getStyle(_("text2"),'width'))
alert(getStyle(_("text2"),'padding-left'))
};