作者:nuysoft/高雲 QQ:47214707 Email:nuysoft@gmail.com
聲明:本文為原創文章,如需轉載,請註明來源並保留原文連結。
jQuery源碼分析系列(持續更新)
樣式表
概述
如何使用
使用詳解
特性對應的實現原理
源碼分析
.css( name, value )
jQuery.style( elem, name, value, extra )
jQuery.css( elem, name, extra )
curCSS( elem, name )
概述
CSS操作部分的源碼分析基於版本1.7.1,以後的jQuery源碼分析系列將採用最新的版本。
jQuery.fn.css()主要解決了三個問題:
瀏覽器安全色:IE、W3C
相容HTML樣式屬性和DOM樣式屬性:連詞符、駝峰
使設定元素的樣式屬性變得快速簡單:動態參數檢測、設定和讀取用統一的介面
如何使用
jQuery.fn.css()有4種用法,第一種是讀取樣式屬性值,其餘三種是設定樣式屬性值:
.css( propertyName ) 擷取第一個元素的樣式屬性值,propertyName是CSS屬性名稱
.css( propertyName, value ) 在匹配的元素集上設定一個CSS屬性,value是要設定的屬性值
.css( propertyName, function(index, value) ) 將函數傳回值做為屬性值設定
function(index, value) 返回要設定的屬性值,函數的上下文this指向當前元素,接收兩個參數:index是當前元素在集合中的下標位置;value是舊值,即當前值(意味著設定之前要先取出當前值)
.css( map ) 設定多個樣式
map 含有索引值對的map,鍵是屬性名稱,值是字面直接量或函數
使用詳解
讀取和設定樣式表遇到的難題是瀏覽器安全色性。例如訪問樣式屬性時的方式就不同:
// 在基於標準的瀏覽器中var defaultView = elem && elem.ownerDocument.defaultView;var computedStyle = defaultView && defaultView.getComputedStyle( elem, null );var ret = computedStyle && computedStyle.getPropertyValue( name );return ret;// 相對的在IE中var ret = elem.currentStyle && elem.currentStyle[ name ]return ret;
另一個常見的相容問題是,某些屬性在不同的瀏覽器中使用不同的屬性名稱,例如float,在IE的DOM實現中用styleFloat,而在遵守W3C標準的瀏覽器中是 cssFloat。
jQuery的.css()方法封裝了這些差異,無論使用什麼屬性名稱都返回相同的結果。例如,一個向左浮動的元素,下邊的三行代碼每行都會返回字串left:
$('div.left').css('float');$('div.left').css('cssFloat');$('div.left').css('styleFloat');
如果遇到由多個單片語成的屬性,這些屬性在CSS和DOM有著不一樣的格式,jQuery也能等價的解釋,例如:
.css( { 'background-color': '#ffe', 'border-left': '5px solid #ccc' } ).css( { backgroundColor: '#ffe', borderLeft: '5px solid #ccc' } )
jQuery都能識別並返回正確的值,注意在DOM屬性的引號是可選的,而CSS屬性必須有引號,因為在屬性名稱中有連字號-。
當使用.css()設定樣式時,jQuery改變元素的樣式屬性style property,例如下面兩行代碼是等價的:
$('#mydiv').css('color', 'green')document.getElementById('mydiv').style.color = 'green'
為樣式屬性設定一個Null 字元串,例如$('#mydiv').css('color', ''),如果這個屬性是行內樣式(HTML style attribute),這個屬性會被從元素的style中移除,無論是通過.css()方法操作,還是直接操作DOM樣式屬性style(DOM style property);但是如果是定義在外部樣式表stylesheet或內部樣式表<style>元素中則不會移除(jQuery的實現並不會修改外部樣式表和內部樣式表,這一點並不像有些書上寫的,儘管瀏覽器提供了原生API支援)。
如果遇到CSS color,不同的瀏覽器可能返回邏輯上相等但是字面上不同的顏色值,總共有四種格式:#FFF、#ffffff、rgb(255,255,255)、blue。
但是.css()不支援CSS屬性縮寫,例如margin background border。例如,如果想要擷取外邊距,需要使用$(elem).css('marginTop') 和 $(elem).css('marginRight'),其他以此類推。
從jQuery1.6開始,.css()可以支援相對值,就像.animate()。相對值是以+=或-=開頭的字串,表示對當前值增加或減少。例如:一個元素的padding-left是10px,.css("padding-left", "+=15")使padding-left變為25px。
從jQuery1.4開始,.css()允許傳入一個函數作為屬性值,例如在下面的這個例子中,將匹配元素的寬度設定為不斷增大的值(遞增):
$('elem.example').css('width', function(index) { return index * 50;});
注意:如果函數沒有返回任何值(例如function( index, style ){}),或返回undefined,當前值不會改變。這一點很有用,如果需要只要當滿足一定條件時,選擇性的設定屬性值時(函數不傳回值或返回undefined,與返回Null 字元串,有著截然不同的處理邏輯和結果)。
最後補充一點CSS的基礎知識,參考http://wenku.baidu.com/view/d9a18f7e27284b73f2425089.html:
1. CSS樣式表有三種寫法:行內樣式、文檔內部樣式、文檔外部樣式
2. 樣式優先順序:行內樣式> 內部樣式 > 外部樣式,ID選取器 > class選取器
將以上特性對應的實現原理簡單闡述下(後邊的源碼分析會詳細的解釋):
設定和讀取用都通過.css()
通過調用多功能工具函數jQuery.access(詳見03 構造jQuery對象-工具函數)
訪問樣式屬性時的方式不同
jQuery載入執行時檢測瀏覽器特性,將getComputedStyle或currentStyle統一為jQuery內部方法curCSS()
某些屬性在不同的瀏覽器中使用不同的屬性名稱
jQuery.cssProps中定義了屬性名稱之間的映射關係
多個單片語成的樣式屬性在CSS和DOM有著不一樣的格式
通過方法jQuery.camelCase()將連詞符格式轉為駝峰格式
相對值
通過jQuery內部正則rrelNum = /^([\-+])=([\-+.\de]+)/檢測並提取運算子和相對值,然後計算
函數的傳回值作為屬性值
通過調用多功能工具函數jQuery.access執行函數,並將傳回值傳給jQuery.style
源碼分析
.css( name, value )
jQuery.fn.css = function( name, value ) {// Setting 'undefined' is a no-op// 兩個參數,value為undefined,則不做任何操作,返回this// 即如果將一個樣式屬性設為undefined,不做任何操作if ( arguments.length === 2 && value === undefined ) {return this;}// access: function( elems, key, value, exec, fn, pass ) {// 調用多功能工具函數jQuery.access,對this進行遍曆,並執行參數中的函數return jQuery.access( this, name, value, true, function( elem, name, value ) {return value !== undefined ?jQuery.style( elem, name, value ) : // 設值,包括value是Null 字元串jQuery.css( elem, name ); // 取值});};
.css()依賴於三個方法:
jQuery.access() 這個全域方法支援.css()、.attr()、.prop(),分析詳見03 構造jQuery對象-工具函數
jQuery.style() 在DOM節點上讀取或設定樣式屬性(style property)
jQuery.css() 在DOM元素上讀取DOM樣式值
馬上開始剖析jQuery.style()和jQuery.css()。
jQuery.style( elem, name, value, extra )
jQuery.style()負責在DOM節點上讀取或設定樣式屬性style property(事實上在CSS模組中只用了jQuery.style()的設定功能,讀取功能和jQuery.css()有什麼區別嗎?有待繼續研究!)。
這個方法大致做了如下事:
1. 過濾Text和Comment,過濾無style的元素,返回undefined
2. 轉換為駝峰式,修正屬性名稱
3. 如果是設定:
如果是number,過濾NaN;過濾null;如果是相對值字串,計算
添加尾碼
如果存在鉤子,則調用鉤子的set;如果沒有鉤子,則設定style[ name ] = value;
4. 如果是讀取:
如果存在鉤子,則調用鉤子的get;如果沒有鉤子,則返回style[ name ]
看看源碼注釋:
// Get and set the style property on a DOM Node// 在DOM節點上讀取或設定樣式屬性style propertystyle: function( elem, name, value, extra ) {// Don't set styles on text and comment nodes// 過濾Text和Comment,如果沒有style屬性也返回if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {return; // undefined}// Make sure that we're working with the right name// 確保使用了正確的名字var ret, type, origName = jQuery.camelCase( name ),style = elem.style, hooks = jQuery.cssHooks[ origName ]; // 轉換為駝峰格式// 修正屬性名稱,是否在不同的瀏覽器中使用不同的屬性名稱name = jQuery.cssProps[ origName ] || origName; // CSS鉤子// Check if we're setting a value// 設定if ( value !== undefined ) {type = typeof value;// convert relative number strings (+= or -=) to relative numbers. #7345// 計算相對值 rrelNum = /^([\-+])=([\-+.\de]+)/, //if ( type === "string" && (ret = rrelNum.exec( value )) ) {/* * ret[1] 正負;ret[2] 相對值 * +( ret[1] + 1) ret[1]是字串,加上1變成'+1'或'-1',最前邊的加號將字串轉換為數字1或-1 * +ret[2] 同樣的加號將ret[2]轉換為數字 * 正負1 乘以 相對值 再加上 當前值,得出要設定的值 */value = ( +( ret[1] + 1) * +ret[2] ) + parseFloat( jQuery.css( elem, name ) );// Fixes bug #9237// #9237:.css()在帶有連字號的屬性上不能工作,在1.6.2中修正type = "number";}// Make sure that NaN and null values aren't set. See: #7116// 過濾NaN null,不做任何處理,如果想從內聯樣式中刪除某個屬性,請傳入Null 字元串if ( value == null || type === "number" && isNaN( value ) ) {return;}// If a number was passed in, add 'px' to the (except for certain CSS properties)// 如果傳入一個數字,追加單位px(jQuery.cssNumber中定義的屬性除外,見jQuery.cssNumber的定義)if ( type === "number" && !jQuery.cssNumber[ origName ] ) {value += "px";}// 前邊的都是前戲:過濾非法參數、計算相對值、追加單位尾碼// If a hook was provided, use that value, otherwise just set the specified value/* * 如果有鉤子hooks,且hooks中存在set函數,則調用hooks.set,將傳回值賦給value * 如果hooks.set的傳回值為undefined,則不執行任何操作;傳回值不為undefined,則用新value設定樣式值 * 簡單點說,有hooks.set則調用,用傳回值替換value,最後設定style.name;否則直接設定style.name * 可見鉤子的作用是修正屬性值,並不直接對值進行設定 * 等價的邏輯: * <pre> * if ( hooks && "set" in hooks ) { * value = hooks.set( elem, value ); * if( value != undefined ) style[ name ] = value; * } else { * style[ name ] = value; * } * </pre> */if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {// Wrapped to prevent IE from throwing errors when 'invalid' values are provided// Fixes bug #5509// 用try-catch塊,預防在IE中,當用不合法的值設定樣式值時,拋出異常try {style[ name ] = value;} catch(e) {}}// 讀取} else {// If a hook was provided get the non-computed value from there// 如果有鉤子hooks,則調用hooks.get,傳回值賦給retif ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {return ret;}// Otherwise just get the value from the style object// 否則從style對象中讀取屬性值return style[ name ];}}
jQuery.css( elem, name, extra )
jQuery.css() 負責讀取樣式值。
這個方法大致做了如下事:
1. 轉換為駝峰式,修正屬性名稱
2. 如果有鉤子,則調用鉤子的get
3. 否則調用curCSS,不同的瀏覽器調用不同的方法:
IE:getComputedStyle,elem.ownerDocument.defaultView.getComputedStyle( elem, null ).getPropertyValue( name )
W3C:currentStyle,elem.currentStyle[ name ]
看看源碼注釋:
// 讀取樣式值css: function( elem, name, extra ) {var ret, hooks;// Make sure that we're working with the right namename = jQuery.camelCase( name ); // 轉換為駝峰式hooks = jQuery.cssHooks[ name ]; // 是否有鉤子name = jQuery.cssProps[ name ] || name; // 修正屬性名稱// cssFloat needs a special treatment// cssFloat需要特殊處理,(styleFloat不需要嗎?)if ( name === "cssFloat" ) {name = "float"; // 又把它轉換回去了!}// If a hook was provided get the computed value from there// 如果鉤子hooks存在,則調用hooks.get計算樣式值,並返回if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {return ret;// Otherwise, if a way to get the computed value exists, use that// 否則,如果curCSS存在,則調用curCSS擷取計算後的樣式值,並返回} else if ( curCSS ) {return curCSS( elem, name );}}
curCSS( elem, name )
/** * 標準 */if ( document.defaultView && document.defaultView.getComputedStyle ) {getComputedStyle = function( elem, name ) {var ret, defaultView, computedStyle; // 預定義變數// 將駝峰式轉換為連字號,例如marginTop > margin-top// rupper = /([A-Z]|^ms)/g,name = name.replace( rupper, "-$1" ).toLowerCase();/* * 分解: * var defaultView = elem && elem.ownerDocument.defaultView; * var computedStyle = defaultView && defaultView.getComputedStyle( elem, null ); * var ret = computedStyle && computedStyle.getPropertyValue( name ); * return ret; */if ( (defaultView = elem.ownerDocument.defaultView) &&(computedStyle = defaultView.getComputedStyle( elem, null )) ) {ret = computedStyle.getPropertyValue( name );// 看不懂這行在幹什嗎?if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {ret = jQuery.style( elem, name );}}return ret;};}/** * IE */if ( document.documentElement.currentStyle ) {currentStyle = function( elem, name ) {var left, rsLeft, uncomputed,ret = elem.currentStyle && elem.currentStyle[ name ], // 直接就取值style = elem.style;// Avoid setting ret to empty string here// so we don't default to auto/* * 避免返回Null 字元串,看不懂? * 如果elem.currentStyle[ name ]返回null,用style[name]試試 */if ( ret === null && style && (uncomputed = style[ name ]) ) {ret = uncomputed;}// From the awesome hack by Dean Edwards// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291// If we're not dealing with a regular pixel number// but a number that has a weird ending, we need to convert it to pixels/* * 不處理一般的像素值,但是如果單位很奇怪就需要修正為像素px * rnumpx = /^-?\d+(?:px)?$/i, // 可選的負號 加 數字 加 可選的px,對數值進行檢查 * rnum = /^-?\d/, // 整數,不支援+1這樣的寫法(應該支援) * * 數字後跟了非像素單位 * * 後邊的看不懂啊,應該是修正單位、auto、fontSize */if ( !rnumpx.test( ret ) && rnum.test( ret ) ) {// Remember the original values// 記錄原始值left = style.left;rsLeft = elem.runtimeStyle && elem.runtimeStyle.left; // // Put in the new values to get a computed value outif ( rsLeft ) { elem.runtimeStyle.left = elem.currentStyle.left;}style.left = name === "fontSize" ? "1em" : ( ret || 0 );ret = style.pixelLeft + "px";// Revert the changed valuesstyle.left = left;if ( rsLeft ) {elem.runtimeStyle.left = rsLeft;}}return ret === "" ? "auto" : ret;};}curCSS = getComputedStyle || currentStyle;
後記:本人對CSS能熟練使用但不精通,看源碼的過程遇到很多疑問,不懂的地方文中有標記,各位同學如果能解疑或有好的參考資料多多拍磚。本人、本文、本系列均不是權威,請持懷疑態度。