2017年3月17日 星期五

jq_4

jQuery源碼解析(4)—— css樣式、定位屬性

標籤: jquerycss源碼定位
 831人閱讀 評論(1) 收藏 舉報
 分類:
目錄(?)[+]

閒話

原計畫是沒有這篇博文的,研究animation源碼的時候遇到了css樣式這個攔路虎。比如jQuery支持「+=10」、「+=10px」定義一個屬性的增量,但是有的屬性設置時可以支持數字,有的必須有單位;在對屬性當前值讀取時,不同的瀏覽器可能返回不同的單位值,無法簡單的相加處理;在能否讀取高寬等位置信息上,還會受到display狀態的影響;不同瀏覽器,相同功能對應的屬性名不同,可能帶有私有前綴等等。
眾多的疑問,讓我決定先破拆掉jQuery的樣式機制。
(本文採用 1.12.0 版本進行講解,用 #number 來標註行號)

css

jQuery實現了一套簡單統一的樣式讀取與設置的機制。$(selector).css(prop)讀取,$(selector).css(prop, value)寫入,也支持對象參數、映射寫入方式。厲害的是,這種簡單高效的用法,完全不用考慮兼容性的問題,甚至包括那些需要加上前綴的CSS3屬性。
/* 讀取 */
$('#div1').css('lineHeight')

/* 寫入 */
$('#div1').css('lineHeight', '30px')
// 映射(這種寫法其實容易產生bug,不如下面一種,後文會講到)
$('#div1').css('lineHeight', function(index, value) {
    return (+value || 0) + '30px';
})
// 增量(只支持+、-,能夠自動進行單位換算,正確累加)
$('#div1').css('lineHeight', '+=30px')
// 對象寫法
$('#div1').css({
    'lineHeight': '+=30px' ,
    'fontSize': '24px'
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
如何統一一個具有眾多兼容問題的系統呢?jQuery的思路是抽象一個標準化的流程,然後對每一個可能存在例外的地方安放鉤子,對於需要例外的情形,只需外部定義對應的鉤子即可調整執行過程,即標準化流程 + 鉤子
下面我們來逐個擊破! 

1、access

jQuery.fn.css( name, value )的功能是對樣式的讀取和寫入,屬於外部使用的外觀方法。內部的核心方法是jQuery.css( elem, name, extra, styles )jQuery.style( elem, name, value, extra )
jq中鏈式調用、對象寫法、映射、無value則查詢這些特點套用在了很多API上,分成兩類。比如第一類:jQuery.fn.css(name, value)、第二類:jQuery.fn.html(value),第二類不支持對象參數寫法。jq抽離了不變的邏輯,抽象成了access( elems, fn, key, value, chainable, emptyGet, raw )入口。
難點(怪異的第二類)
第一類(有key,bulk = false)
普通(raw):對elems(一個jq對象)每一項->fn(elems[i], key, value)
映射(!raw):對elems每一項elems[i],求得key屬性值val=fn(elems[i], key),執行map(即value)函數value.call( elems[ i ], i, val )得到返回值re,執行fn(elems[i], key, re)
取值(!value):僅取第一項fn( elems[ 0 ], key )
第二類(無key,bulk = true)
普通(raw):直接fn.call(elems, value)
映射(!raw):對elems每一項elems[i],求得值val=fn.call( jQuery( elems[i] )),執行map(即value)函數value.call( jQuery(elems[ i ]), val )得到返回值re,執行fn.call( jQuery( elems[i] ), re)
取值(!value):取fn.call( elems )
正是這兩類的不同造成了access內部邏輯的難懂,下面代碼中第二類進行fn封裝,就是為了bulk->!raw->map能夠與第一類使用同樣的邏輯。兩類的使用方法,包括映射參數寫法都是不同的。有value均為鏈式調用chainable=true
// #4376
var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
    var i = 0,
        length = elems.length,
        // 確定哪一類,false為第一類
        bulk = key == null;

    // 第一類對象寫法,為設置,開啟鏈式
    if ( jQuery.type( key ) === "object" ) {
        chainable = true;
        // 拆分成key-value式寫法,靜待鏈式返回
        for ( i in key ) {
            access( elems, fn, i, key[ i ], true, emptyGet, raw );
        }

    // value有值,為設置,開啟鏈式
    } else if ( value !== undefined ) {
        chainable = true;

        if ( !jQuery.isFunction( value ) ) {
            raw = true;
        }

        if ( bulk ) {

            // bulk->raw 第二類普通賦值,靜待鏈式返回
            if ( raw ) {
                fn.call( elems, value );
                fn = null;

            // bulk->!raw 第二類map賦值,封裝,以便能使用第一類的式子
            } else {
                bulk = fn;
                fn = function( elem, key, value ) {
                    return bulk.call( jQuery( elem ), value );
                };
            }
        }

        if ( fn ) {
            for ( ; i < length; i++ ) {
                // 第一類raw普通,!raw映射。封裝後的第二類共用映射方法
                fn(
                    elems[ i ],
                    key,
                    raw ? value : value.call( elems[ i ], i, fn( elems[ i ], key ) )
                );
            }
        }
    }

    return chainable ?
        // 賦值,鏈式
        elems :

        // 取值
        bulk ?
            // 第二類
            fn.call( elems ) :
            // 第一類
            length ? fn( elems[ 0 ], key ) : emptyGet;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
讀取依賴window上的getComputedStyle方法,IE6-8依賴元素的currentStyle方法。樣式的寫入依賴elem.style。 

2、jQuery.fn.css

jQuery.fn.css( name, value )為什麼會有兩個核心方法呢?因為樣式的讀取和寫入不是同一個方式,而寫入的方式有時候也會用來讀取。
讀:依賴window上的getComputedStyle方法,IE6-8依賴元素的currentStyle方法。內聯外嵌的樣式都可查到
寫:依賴elem.style的方式。而elem.style方式也可以用來查詢的,但是只能查到內聯的樣式
因此封裝了兩個方法jQuery.css( elem, name, extra, styles )jQuery.style( elem, name, value, extra ),前者只讀,後者可讀可寫,但是後者的讀比較雞肋,返回值可能出現各種單位,而且還無法查到外嵌樣式,因此jQuery.fn.css方法中使用前者的讀,後者的寫
// #7339
jQuery.fn.css = function( name, value ) {
    // access第一類用法,fn為核心函數的封裝,直接看返回值
    return access( this, function( elem, name, value ) {
        var styles, len,
            map = {},
            i = 0;

        // 增加一種個性化的 取值 方式。屬性數組,返回key-value對象
        // 第一類取值,只取elems[0]對應fn執行的返回值
        if ( jQuery.isArray( name ) ) {
            styles = getStyles( elem );
            len = name.length;

            for ( ; i < len; i++ ) {
                // false參數則只返回未經處理的樣式值,給定了styles則從styles對象取樣式
                // 下面會單獨講jQuery.css
                map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
            }

            return map;
        }

        return value !== undefined ?
            // 賦值
            jQuery.style( elem, name, value ) :
            // 取值
            jQuery.css( elem, name );
    }, name, value, arguments.length > 1 );
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

3、屬性名兼容

第一步:jq支持駝峰和』-『串聯兩種寫法,會在核心方法中統一轉化為小駝峰形式
第二步:不同瀏覽器的不同屬性名兼容,如float為保留字,標準屬性是cssFloat,IE中使用styleFloat(當前版本IE已拋棄)。查看是否在例外目錄中
第三步:css3屬性支持程度不一,有的需要加上私有前綴才可使用。若加上私有前綴才能用,添加到例外目錄中方便下次拿取
// #83,#356,第一步,小駝峰
rmsPrefix = /^-ms-/,
rdashAlpha = /-([\da-z])/gi,
fcamelCase = function( all, letter ) {
    return letter.toUpperCase();
};
// #356
camelCase = function( string ) {
    return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
},


// #7083,第二步
cssProps: {

    // normalize float css property
    "float": support.cssFloat ? "cssFloat" : "styleFloat"
}


// #6854,第三步 
cssPrefixes = [ "Webkit", "O", "Moz", "ms" ],
emptyStyle = document.createElement( "div" ).style;

// return a css property mapped to a potentially vendor prefixed property
function vendorPropName( name ) {

    // 查詢無前綴駝峰名是否支持
    if ( name in emptyStyle ) {
        return name;
    }

    // 首字母變為大寫
    var capName = name.charAt( 0 ).toUpperCase() + name.slice( 1 ),
        i = cssPrefixes.length;

    // 查找是否有支持的私有前綴屬性
    while ( i-- ) {
        name = cssPrefixes[ i ] + capName;
        if ( name in emptyStyle ) {
            return name;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

4、jQuery.css、jQuery.style

jq的樣式機制之所有複雜,因為在核心方法功能的設計上,考慮了非必要的「易用」、「兼容」、「擴展」。使得設置更靈活、輸出更一致、支持累加換算、支持拓展的功能。由於對下面特性的支持,因此對樣式的取值抽象出了核心邏輯curCSS( elem, name, computed ),一來是邏輯劃分更清晰,內部更可根據需要酌情選擇使用這兩者
易用
1、為了提高易用性,jQuery.style()可以自動為設置值加上默認單位』px',由於有些屬性值可以為數字,因此定義了cssNumber的列表,列表中的項目不會加上默認單位。
2、允許增量』+=20px'式寫法,由於採用jQuery.css獲取的初始值單位有可能不同,因此封裝了一個自動單位換算並輸出增量後最終結果的函數adjustCSS()
兼容
並不是每個屬性都能返回預期的值。
1、比如opacity在IE低版本是filter,用jQuery.style方式取值時需要匹配其中數字,結果跟opacity有100倍差距,而且設置的時候alpha(opacity=num)的形式也太獨特。
2、比如定位信息會因為元素display為none等狀態無法正確獲取到getBoundingClientRect()、offsetLeft、offsetWidth等位置信息及大小,而且對於自適應寬度無法取得寬高信息。
3、比如一些瀏覽器兼容問題,導致某些元素返回百分比等非預期值。
jQuery.cssHooks是樣式機制的鉤子系統。可以對需要hack的屬性,添加鉤子,在jQuery.css、jQuery.style讀取寫入之前,都會先看是否存在鉤子並調用,然後決定是否繼續下一步還是直接返回。通過在setget屬性中定義函數,使得行為正確一致。
擴展
1、返回值:jQuery.css對樣式值的讀取,可以指定對於帶單位字符串和」auto」等字符串如何返回,新增了extra參數。為」「(不強制)和true(強制)返回去單位值,false不做特殊處理直接返回。
2、功能擴展:jq允許直接通過innerWidth()/innerHeight()、outerWidth()/outerHeight()讀取,也支持賦值,直接調整到正確的寬高。這是通過extra指定padding、border、margin等字符串做到的
3、cssHooks.expand:對於margin、padding、borderWidth等符合屬性,通過擴展expand接口,可以得到含有4個分屬性值的對象。
bug
使用字符串』30』和數字30的效果有區別。對於不能設為數字的,數字30自動加上px,字符串的卻不會。
下面adjustCSS換算函數中也提到一個bug,下面有描述。
建議:adjustCSS函數本身就可以處理增量和直接量兩種情況,type===』string'判斷的地方不要ret[ 1 ],以解決第一個問題。adjustCSS返回一個數組,第一個為值,第二個為單位,這樣就防止第二個bug。
// #4297,pnum匹配數字,rcssNum -> [匹配項,加/減,數字,單位]
var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;
var rcssNum = new RegExp( "^(?:([+-])=)(" + pnum + ")([a-z%]*)$", "i" );

// #6851
cssNormalTransform = {
    letterSpacing: "0",
    fontWeight: "400"
}

// #7090,核心方法
jQuery.extend( {
    // 支持數字參數的屬性列表,不會智能添加單位
    cssNumber: {
        "animationIterationCount": true,
        "columnCount": true,
        "fillOpacity": true,
        "flexGrow": true,
        "flexShrink": true,
        "fontWeight": true,
        "lineHeight": true,
        "opacity": true,
        "order": true,
        "orphans": true,
        "widows": true,
        "zIndex": true,
        "zoom": true
    },

    // elem.style方式讀寫
    style: function( elem, name, value, extra ) {

        // elem為文本和註釋節點直接返回
        if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
            return;
        }

        /**
         * ---- 1、name修正,屬性兼容 ---- 
         */
        var ret, type, hooks,
            // 小駝峰
            origName = jQuery.camelCase( name ),
            style = elem.style;

        // 例外目錄、私有前綴
        name = jQuery.cssProps[ origName ] ||
            ( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName );

        // 鉤子
        // 先name、後origName使鉤子更靈活,既可統一,又可單獨
        hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];

        /**
         *  ---- 2、elem.style方式 - 賦值 ---- 
         */
        if ( value !== undefined ) {
            type = typeof value;

            // '+='、'-='增量運算
            // Convert "+=" or "-=" to relative numbers (#7345)
            if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
                // adjustCSS對初始值和value進行單位換算,相加/減得到最終值(數值)
                value = adjustCSS( elem, name, ret );

                // 數值需要在下面加上合適的單位
                type = "number";
            }

            // Make sure that null and NaN values aren't set. See: #7116
            if ( value == null || value !== value ) {
                return;
            }

            // 數值和'+=xx'轉換的數值,都需要加上單位。cssNumber記錄了可以是數字的屬性,否則默認px
            // ret[3]為'+=xx'原本匹配的單位
            if ( type === "number" ) {
                value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
            }

            // Fixes #8908, it can be done more correctly by specifing setters in cssHooks,
            // but it would mean to define eight
            // (for every problematic property) identical functions
            if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
                style[ name ] = "inherit";
            }

            // 有鉤子先使用鉤子,看返回值是否為undefined決定是否style[ name ]賦值,否則直接賦值
            if ( !hooks || !( "set" in hooks ) ||
                ( value = hooks.set( elem, value, extra ) ) !== undefined ) {

                // Support: IE
                // Swallow errors from 'invalid' CSS values (#5509)
                try {
                    style[ name ] = value;
                } catch ( e ) {}
            }

        /**
         *  ---- 3、elem.style方式 - 取值 ---- 
         */
        } else {

            // 有鉤子先使用鉤子,看返回值是否為undefined決定是否style[ name ]取值,否則直接取值
            if ( hooks && "get" in hooks &&
                ( ret = hooks.get( elem, false, extra ) ) !== undefined ) {

                return ret;
            }

            return style[ name ];
        }
    },

    // 默認computedStyle/currentStyle方式只讀,也可styles指定讀取對象
    css: function( elem, name, extra, styles ) {

        /**
         * ---- 1、name修正,屬性兼容(同style) ---- 
         */
        var num, val, hooks,
            origName = jQuery.camelCase( name );

        name = jQuery.cssProps[ origName ] ||
            ( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName );

        // 鉤子
        hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];

        // 若有鉤子,通過鉤子讀取
        if ( hooks && "get" in hooks ) {
            val = hooks.get( elem, true, extra );
        }

        // 沒有鉤子,通過封裝的curCSS讀取
        if ( val === undefined ) {
            val = curCSS( elem, name, styles );
        }

        // 屬性值為"normal",若為cssNormalTransform內的屬性,把對應值輸出
        if ( val === "normal" && name in cssNormalTransform ) {
            val = cssNormalTransform[ name ];
        }

        // extra === "" 去單位處理,若為"normal"、"auto"等字符串,原樣返回
        // extra === true 強制去單位,若為parseFloat後為NaN的字符串,返回0
        // extra === false/undefined 不特殊處理
        if ( extra === "" || extra ) {
            num = parseFloat( val );
            return extra === true || isFinite( num ) ? num || 0 : val;
        }
        return val;
    }
} );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154

5、adjsutCSS換算

adjustCSS( elem, prop, valueParts, tween )用於調用jQuery.style對增量計算的換算,並得到最終值。在jq內部,除了css樣式會換算,動畫處理也支持換算。這裡也可以把動畫的tween對象的初始值和增量進行累加換算,得到最終值賦給tween對象
難點:
這裡需要知道jQuery.css( elem, prop, "" )通過computedStyle/currentStyle求得的值單位不變,並且被extra=」「去掉了單位。比如初始值是30px,增量為』+=1rem',先使用增量的單位30rem,然後調用jQuery.css查詢跟修改前的初始值比較,比如變成了scale=15倍,則30rem/15=2rem求得原值換算後為2rem,然後再累加返回3rem。
maxIterations設為20有兩個原因:1、js浮點誤差可能導致兩邊總是不相等;2、對於首次調整單位變成了很小的倍數趨近於0無法計算,則通過重置為0.5每次乘2直到可以計算,慢慢的調整差距
bug(建議見第4點):
cssNumber列表中屬性的值使用無單位增量如』+=10』,而初始值單位為px,將按照初始值單位』+=10px'處理後返回。但返回到外部,由於在cssNumber列表中,並不會再次加上單位,按照倍數被設置了。
比如lineHeight初始值20px,使用』+=4』,變成了賦值24倍
// valueParts為增量匹配結果集,兩種形式
// 動畫 adjustCSS(tween.elem, prop, rcssNum.exec( value ), tween)
// css adjustCSS(elem, prop, rcssNum.exec( value ))
function adjustCSS( elem, prop, valueParts, tween ) {
    var adjusted,
        // 默認比例
        scale = 1,
        // 最大修正次數
        maxIterations = 20,
        currentValue = tween ?
            // 動畫對象當前屬性值計算
            function() { return tween.cur(); } :
            function() { return jQuery.css( elem, prop, "" ); },
        // 當前用作累加基數的初始值
        initial = currentValue(),
        // 匹配單位,若不在cssNumber目錄,並且沒帶單位,則當做px
        unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),

        // 由於初始值若匹配到單位,都會是px,不是的在執行css過程中jq也有鉤子修正,所以有可能需要換算的只有cssNumber列表中項目,或者unit不為px且initial有非0數值的(0無需換算)。初始值為字符串如"auto",則會在下面按照0處理
        initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) &&
            rcssNum.exec( jQuery.css( elem, prop ) );

    // 單位不同時換算
    if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {

        // 默認使用增量值單位
        // 若為cssNumber中屬性,且增量無單位,則使用初始值單位,後面也無需換算了
        // 小bug:cssNumber列表中屬性'+=10'若無unit,按照初始值單位'+=10px'處理返回。但返回到外部,由於在cssNumber列表中,並不會再次加上單位,按照倍數被設置了。比如lineHeight初始值20px,使用'+=4',變成了賦值24倍
        unit = unit || initialInUnit[ 3 ];

        // Make sure we update the tween properties later on
        valueParts = valueParts || [];

        // Iteratively approximate from a nonzero starting point
        // 此處個人覺得沒有 || 1 的寫法沒有必要性,若為0,則無需換算了
        initialInUnit = +initial || 1;

        // 換算,見難點解釋
        do {

            // If previous iteration zeroed out, double until we get *something*.
            // Use string for doubling so we don't accidentally see scale as unchanged below
            scale = scale || ".5";

            // Adjust and apply
            initialInUnit = initialInUnit / scale;
            jQuery.style( elem, prop, initialInUnit + unit );

        // Update scale, tolerating zero or NaN from tween.cur()
        // Break the loop if scale is unchanged or perfect, or if we've just had enough.
        } while (
            scale !== ( scale = currentValue() / initial ) && scale !== 1 && --maxIterations
        );
    }

    if ( valueParts ) {
        // 初始值為字符串,也將按照0處理,需要注意咯
        initialInUnit = +initialInUnit || +initial || 0;

        // 根據是否為增量運算判斷直接賦值還是換算後的初始值與增量相加,css運算中只允許增量運算使用該函數
        adjusted = valueParts[ 1 ] ?
            initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :
            +valueParts[ 2 ];
        // 對動畫對象賦初值和末值
        if ( tween ) {
            tween.unit = unit;
            tween.start = initialInUnit;
            tween.end = adjusted;
        }
    }
    return adjusted;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72

6、curCSS、getStyles

curCSS( elem, name, computed )是對getStyles( elem )的封裝,可以通過computed指定樣式對象替代內部的getStyle。對高版本瀏覽器和低版本IE getStyle分別使用的getComputedStyle、currentStyle,前者是全局對象下的屬性,所以源碼中使用了ownerDocument.defaultView指代。
不同的瀏覽器,對屬性的返回可能出現百分比等非px返回值,jq通過鉤子處理個體,curCSS內部也處理了一些情況,比如Chrome、Safari的margin相關屬性值返回百分比,低版本IE的非top等位置屬性返回百分比等。
// #6489,下面會用到的正則
var rmargin = ( /^margin/ );
var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );

// #6692
var getStyles, curCSS,
    rposition = /^(top|right|bottom|left)$/;

// 高版本瀏覽器
if ( window.getComputedStyle ) {
    getStyles = function( elem ) {

        // Support: IE<=11+, Firefox<=30+ (#15098, #14150)
        // IE throws on elements created in popups
        // FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
        var view = elem.ownerDocument.defaultView;

        // opener指的是打開該頁面的源頁面的window
        if ( !view || !view.opener ) {
            view = window;
        }

        return view.getComputedStyle( elem );
    };

    // 默認使用getStyles,也可通過computed參數指定樣式對象。內部還有對文檔片段和margin類屬性值的特殊處理
    curCSS = function( elem, name, computed ) {
        var width, minWidth, maxWidth, ret,
            style = elem.style;

        computed = computed || getStyles( elem );

        // getPropertyValue is only needed for .css('filter') in IE9, see #12537
        // getComputedStyle(elem).getPropertyValue(name)其實也可以用來獲取屬性,但是不支持駝峰,必須-連接書寫,否則返回""
        ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined;

        // 文檔片段document fragments的元素通過getComputedStyle取樣式是""或undefined,需要退回到style方式取
        // 文檔片段中的元素elem.ownerDocument不為文檔片段,為docuemnt
        if ( ( ret === "" || ret === undefined ) && !jQuery.contains( elem.ownerDocument, elem ) ) {
            ret = jQuery.style( elem, name );
        }

        if ( computed ) {

            // 為了兼容有的瀏覽器margin相關方法返回百分比等非px值的情況,由於width輸出是px,並且margin的百分比是按照width計算的,因此可以直接賦值width。設置minWidth/maxWidth是為了保證設置的width不會因為超出限制失效
            if ( !support.pixelMarginRight() && rnumnonpx.test( ret ) && rmargin.test( name ) ) {

                // 記憶
                width = style.width;
                minWidth = style.minWidth;
                maxWidth = style.maxWidth;

                // 把margin的值設置到width,並獲取對應width值作為結果
                style.minWidth = style.maxWidth = style.width = ret;
                ret = computed.width;

                // 還原
                style.width = width;
                style.minWidth = minWidth;
                style.maxWidth = maxWidth;
            }
        }

        // Support: IE
        // IE returns zIndex value as an integer.都以字符串返回
        return ret === undefined ?
            ret :
            ret + "";
    };

// IE 6-8
} else if ( documentElement.currentStyle ) {
    getStyles = function( elem ) {
        return elem.currentStyle;
    };

    curCSS = function( elem, name, computed ) {
        var left, rs, rsLeft, ret,
            style = elem.style;

        computed = computed || getStyles( elem );
        ret = computed ? computed[ name ] : undefined;

        // Avoid setting ret to empty string here
        // so we don't default to auto
        if ( ret == null && style && style[ name ] ) {
            ret = style[ name ];
        }

        // 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
        // but not position css attributes, as those are
        // proportional to the parent element instead
        // and we can't measure the parent instead because it
        // might trigger a "stacking dolls" problem
        // 對非位置top|left|right|bottom返回的,先把left屬性保存,然後把屬性設置到left上,然後取出
        if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {

            // 記憶
            left = style.left;
            // runtimeStyle是低版本IE中表示運行中樣式,可讀可寫,優先級大於style設置的
            rs = elem.runtimeStyle;
            rsLeft = rs && rs.left;

            // Put in the new values to get a computed value out
            if ( rsLeft ) {
                rs.left = elem.currentStyle.left;
            }
            // 對於百分比,是以父元素寬度為基準。而對於fontSize,設置到left的1rem大小則是固定的
            style.left = name === "fontSize" ? "1em" : ret;
            ret = style.pixelLeft + "px";

            // 還原
            style.left = left;
            if ( rsLeft ) {
                rs.left = rsLeft;
            }
        }

        // Support: IE
        // IE returns zIndex value as an integer.數字以字符串形式返回
        return ret === undefined ?
            ret :
            ret + "" || "auto";
    };
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129

7、cssHooks鉤子

正如第4點提到的,cssHooks存在的主要目的是應對存取出現的不一致行為。jq用support對象放置測試後的兼容性信息,源碼#6519 - #6690行有很多support對象的樣式方面兼容測試,內部通過addGetHookIf( conditionFn, hookFn )來綁定鉤子。鉤子有個computed參數,用於標記是jQuery.css/style哪個方法的讀操作觸發的,對應true/false
存:存的不一致只有一個
1、opacity透明度對於IE內部使用filter,需要設置為alpha(opacity=100*value)的形式
*、對於padding、borderWidth、height、width的存只是做了負數變為0的特殊處理。
取:取的不一致比較多
1、opacity的」「按」1」處理,對於需要使用filter的IE低版本也要hook
2、height、width的獲取需要display不為none和帶有table的任意值(除了table、table-cell、table-caption三樣),因此提供了swap( elem, options, callback, args )用於以指定屬性狀態調用函數取值,之後還原狀態
3、marginLeft、marginRight對於不支持返回可靠值的瀏覽器做處理,marginRight在display為」inline-block」下取值,marginLeft通過getBoundingClientRect比對與marginLeft設置為0後的位置差得到
4、top、left中有可能返回百分比的瀏覽器,先取值,若不為px單位,則調用內部position方法計算top、left(但是此方法是相對有定位父集或html的,對於position為relative的是有bug的,個人建議源碼中可以對relative的使用getBoundingClientRect比對處理)
擴展: innerWidth()/innerHeight()/outerWidth()/outerHeight()
盒模型默認是寬高不包括padding、border、margin。css3里有boxSizing屬性,content-box|border-box|inherit分別代表 「不包括padding、border、margin」 | 「包含border和padding」 | 「繼承」。
jq通過innerWidth()/innerHeight()可以直接查詢/設置content-box區域的長寬;通過outerWidth()/outerHeight()可查詢/設置為border-box區域的長寬,增加一個參數true,如([value, ]true),可查詢/設置為border-box區域加上margin區域的總長寬。
jq仍然是設置height、width,不過它會進行換算。通過augmentWidthOrHeight( elem, name, extra, isBorderBox, styles )計算增量(數值),通過getWidthOrHeight( elem, name, extra )得到最終值(帶px字符串)。通過extra來指明按照content、padding、border、margin中哪一個級別。
注意
cssHooks內若要得到自身屬性的樣式,不調用jQuery.css,而是直接調用curCSS,包括getWidthOrHeight內,因為curCSS是純粹的取值,不會調用鉤子造成死循環
/* #1307 contains
 * 節點包含。後面經常用來驗證是否為文檔片段中的元素
---------------------------------------------------------------------- */
// /^[^{]+\{\s*\[native \w/  -> 匹配內部方法 'funtion xxx() { [native code] }'
hasCompare = rnative.test( docElem.compareDocumentPosition );

// 返回布爾值,true表示 b節點在a節點內/a文檔的根節點內(節點相等為false)
// ie9+及其他瀏覽器支持compareDocumentPosition,ie6-8支持contains,比較老的safari都不支持使用下面的函數
contains = hasCompare || rnative.test( docElem.contains ) ?
    function( a, b ) {
        var adown = a.nodeType === 9 ? a.documentElement : a,
            bup = b && b.parentNode;
        return a === bup || !!( bup && bup.nodeType === 1 && (
            adown.contains ?
                adown.contains( bup ) :
                // a.compareDocumentPosition( bup ) = 16表示 a包含b
                a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
        ));
    } :
    function( a, b ) {
        if ( b ) {
            // 若不等於父節點,繼續冒泡知道根節點
            while ( (b = b.parentNode) ) {
                if ( b === a ) {
                    return true;
                }
            }
        }
        return false;
    };


/* #6494 swap
 * elem的style在options狀態下調用callback.apply(elem, args),然後改回原屬性。返回查到的值
---------------------------------------------------------------------- */
var swap = function( elem, options, callback, args ) {
    var ret, name,
        old = {};

    // 記錄原有值,設定上新值
    for ( name in options ) {
        old[ name ] = elem.style[ name ];
        elem.style[ name ] = options[ name ];
    }

    ret = callback.apply( elem, args || [] );

    // 還原舊值
    for ( name in options ) {
        elem.style[ name ] = old[ name ];
    }

    return ret;
};

/* #6816 addGetHookIf
 * conditionFn()執行後返回true,說明支持,不會綁定hookFn鉤子
---------------------------------------------------------------------- */
function addGetHookIf( conditionFn, hookFn ) {

    // Define the hook, we'll check on the first run if it's really needed.
    // 預執行和懶加載的方式均可,源碼選擇了懶加載。第一次當做鉤子執行調用時綁定真實鉤子或刪除
    return {
        get: function() {
            if ( conditionFn() ) {

                // 支持,無需鉤子
                delete this.get;
                return;
            }

            // 需要鉤子,定義為hookFn。即使是第一次也要執行一次
            return ( this.get = hookFn ).apply( this, arguments );
        }
    };
}


/* #6935 setPositiveNumber
 * 保證非負值,保留單位,subtract可以指定需要減去的值
---------------------------------------------------------------------- */
function setPositiveNumber( elem, value, subtract ) {
    var matches = rnumsplit.exec( value );
    return matches ?

        // Guard against undefined "subtract", e.g., when used as in cssHooks
        Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
        value;
}

/* #6944 augmentWidthOrHeight
 * 根據extra類型計算增量(相對於height/width取值),返回純數值
 * 注意:讀取為增量,寫入為減量
---------------------------------------------------------------------- */
function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {

    // border-box -> border, content-box -> content。無需修正為4
    var i = extra === ( isBorderBox ? "border" : "content" ) ?

        // If we already have the right measurement, avoid augmentation
        4 :

        // height: 0(top) 2(bottom)  width: 1(right) 3(left)
        // cssExpand = [ "Top", "Right", "Bottom", "Left"];
        name === "width" ? 1 : 0,

        val = 0;

    for ( ; i < 4; i += 2 ) {

        // border-box  content-box 想變為margin級別都需要 + margin值
        if ( extra === "margin" ) {
            val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
        }

        if ( isBorderBox ) {

            // border-box = content級別 + "padding" + "border"(下面那個)
            if ( extra === "content" ) {
                // true 表示強制去單位
                val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
            }

            // border-box = padding級別 + "border"
            if ( extra !== "margin" ) {
                val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
            }
        } else {

            // 邏輯能走到這裡,說明一定不是content級別,否則 i = 4
            // content-box 變為任意級別都要 + padding 。 true 表示強制去單位
            val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );

            // 變為border級別、margin級別要 + border
            if ( extra !== "padding" ) {
                val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
            }
        }
    }

    return val;
}

/* #6988 getWidthOrHeight
 * 用於讀取,會根據extra加上augmentWidthOrHeight增量
---------------------------------------------------------------------- */
function getWidthOrHeight( elem, name, extra ) {

    // getWidthOrHeight = contentBox級別值 + augmentWidthOrHeight增量
    // 這裡直接用offsetWidth/offsetHeight返回的borderbox級別值作為基礎值,因此下面需要調整,valueIsBorderBox默認值為true,表示為border-box
    var valueIsBorderBox = true,
        val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
        styles = getStyles( elem ),
        // 只有支持boxSizing屬性,且為border-box,isBorderBox才為true,否則要調整val
        isBorderBox = support.boxSizing &&
            jQuery.css( elem, "boxSizing", false, styles ) === "border-box";

    // Support: IE11 only,全屏瀏覽下bug,不瞭解(逃
    // In IE 11 fullscreen elements inside of an iframe have
    // 100x too small dimensions (gh-1764).
    if ( document.msFullscreenElement && window.top !== window ) {

        // Support: IE11 only
        // Running getBoundingClientRect on a disconnected node
        // in IE throws an error.
        if ( elem.getClientRects().length ) {
            val = Math.round( elem.getBoundingClientRect()[ name ] * 100 );
        }
    }

    // some non-html elements return undefined for offsetWidth, so check for null/undefined
    // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
    // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668

    // svg 和 MathML 可能會返回 undefined,需要重新求值
    if ( val <= 0 || val == null ) {

        // 直接獲取 width/height 作為基礎值,若之後調用elem.style,說明support.boxSizingReliable()一定為false
        val = curCSS( elem, name, styles );
        if ( val < 0 || val == null ) {
            val = elem.style[ name ];
        }

        // 匹配到非px且帶單位的值,則直接退出
        if ( rnumnonpx.test( val ) ) {
            return val;
        }

        // valueIsBorderBox意思是得到的value是borderbox級別的,由於調整為了curCSS取值,因此,必須要isBorderBox為true,不可靠值當做content級別處理(因為border、padding容易獲取到準確值,val === elem.style[ name ]除外)
        valueIsBorderBox = isBorderBox &&
            ( support.boxSizingReliable() || val === elem.style[ name ] );

        // Normalize "", auto, and prepare for extra
        // 強制去單位,"auto"等字符串變為0
        val = parseFloat( val ) || 0;
    }

    // use the active box-sizing model to add/subtract irrelevant styles
    return ( val +
        augmentWidthOrHeight(
            elem,
            name,
            // 若沒指定,默認值跟盒模型一致
            extra || ( isBorderBox ? "border" : "content" ),
            // 表示基數val是否為borderBox,extra和它一致說明無需累加
            valueIsBorderBox,
            styles
        )
    ) + "px";
}



/* #7201 cssHooks[ "height", "width" ]
 * 防止設置負數。支持根據extra指定級別修正設定/獲取值
---------------------------------------------------------------------- */
jQuery.each( [ "height", "width" ], function( i, name ) {
    jQuery.cssHooks[ name ] = {
        get: function( elem, computed, extra ) {
            // innerWidth等API內部只調用jQuery.css,style方式不用鉤子,所以false則退出
            if ( computed ) {

                // rdisplayswap = /^(none|table(?!-c[ea]).+)/
                // cssShow = { position: "absolute", visibility: "hidden", display: "block" }
                // display影響了定位信息的獲取,比如offsetWidth為0。先設置cssShow屬性獲取到值,然後改回屬性
                return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
                    elem.offsetWidth === 0 ?
                        swap( elem, cssShow, function() {
                            return getWidthOrHeight( elem, name, extra );
                        } ) :
                        getWidthOrHeight( elem, name, extra );
            }
        },

        set: function( elem, value, extra ) {
            var styles = extra && getStyles( elem );
            // 設置非負值,在設置時增量即為減量,第三個參數對於substract參數
            return setPositiveNumber( elem, value, extra ?
                augmentWidthOrHeight(
                    elem,
                    name,
                    extra,
                    support.boxSizing &&
                        jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
                    styles
                ) : 0
            );
        }
    };
} );

/* #7053 #7233 cssHooks[ "opacity" ]
 * 根據是否支持opacity,判斷內部使用opacity還是filter
---------------------------------------------------------------------- */
cssHooks: {
    opacity: {
        get: function( elem, computed ) {
            // elem.style['opacity']調用無需鉤子,所以false則不處理
            if ( computed ) {

                // We should always get a number back from opacity
                var ret = curCSS( elem, "opacity" );
                return ret === "" ? "1" : ret;
            }
        }
    }
},

// #7233 filter部分
if ( !support.opacity ) {
    jQuery.cssHooks.opacity = {
        // computed -> css(true)、style(false) 均hook
        get: function( elem, computed ) {

            // IE uses filters for opacity
            // ropacity = /opacity\s*=\s*([^)]*)/i,
            return ropacity.test( ( computed && elem.currentStyle ?
                elem.currentStyle.filter :
                elem.style.filter ) || "" ) ?
                    ( 0.01 * parseFloat( RegExp.$1 ) ) + "" :
                    computed ? "1" : "";
        },

        set: function( elem, value ) {
            var style = elem.style,
                currentStyle = elem.currentStyle,
                opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
                filter = currentStyle && currentStyle.filter || style.filter || "";

            // IE has trouble with opacity if it does not have layout
            // Force it by setting the zoom level
            style.zoom = 1;

            // if setting opacity to 1, and no other filters exist -
            // attempt to remove filter attribute #6652
            // if value === "", then remove inline opacity #12685
            if ( ( value >= 1 || value === "" ) &&
                    jQuery.trim( filter.replace( ralpha, "" ) ) === "" &&
                    style.removeAttribute ) {

                // Setting style.filter to null, "" & " " still leave "filter:" in the cssText
                // if "filter:" is present at all, clearType is disabled, we want to avoid this
                // style.removeAttribute is IE Only, but so apparently is this code path...
                style.removeAttribute( "filter" );

                // if there is no filter style applied in a css rule
                // or unset inline opacity, we are done
                if ( value === "" || currentStyle && !currentStyle.filter ) {
                    return;
                }
            }

            // otherwise, set new filter values
            // ralpha = /alpha\([^)]*\)/i
            style.filter = ralpha.test( filter ) ?
                filter.replace( ralpha, opacity ) :
                filter + " " + opacity;
        }
    };
}

/* #7282 cssHooks[ "marginRight" "marginLeft" ]
 * 只對不支持的瀏覽器使用 get 鉤子,通過addGetHookIf
---------------------------------------------------------------------- */
jQuery.cssHooks.marginRight = addGetHookIf( support.reliableMarginRight,
    function( elem, computed ) {
        // 僅css方式
        if ( computed ) {
            return swap( elem, { "display": "inline-block" },
                curCSS, [ elem, "marginRight" ] );
        }
    }
);

jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
    function( elem, computed ) {
        // 僅css方式
        if ( computed ) {
            return (
                parseFloat( curCSS( elem, "marginLeft" ) ) ||

                // Support: IE<=11+
                // Running getBoundingClientRect on a disconnected node in IE throws an error
                // Support: IE8 only
                // getClientRects() errors on disconnected elems
                ( jQuery.contains( elem.ownerDocument, elem ) ?
                    // 與marginLeft=0的left坐標比對差值
                    elem.getBoundingClientRect().left -
                        swap( elem, { marginLeft: 0 }, function() {
                            return elem.getBoundingClientRect().left;
                        } ) :
                    // 文檔片段中的元素按0處理
                    0
                )
            ) + "px";
        }
    }
);

/* #10882 cssHooks[ "top" "left" ]
 * 只對不支持px返回位置的瀏覽器使用 get 鉤子,通過addGetHookIf
---------------------------------------------------------------------- */
// 
jQuery.each( [ "top", "left" ], function( i, prop ) {
    jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
        function( elem, computed ) {
            if ( computed ) {
                computed = curCSS( elem, prop );

                // if curCSS returns percentage, fallback to offset
                // position是相對最近的有定位的祖先節點的偏移,對於position:relative是不合適的,算是個bug吧
                return rnumnonpx.test( computed ) ?
                    jQuery( elem ).position()[ prop ] + "px" :
                    computed;
            }
        }
    );
} );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379

8、innerWidth/Height()、outerWidth/Height()

第7點已經講了很多,增量的處理,並不在這幾個函數中,統一交給了augmentWidthOrHeight,使得邏輯變得簡單統一,很好的實踐了分離可變與不變的思想。
注意:它們不受jQuery.style中的小bug(如200有效,』200』無效)影響。它們不在cssNumber列表,雖然不會再該函數裡為字符串自動補px,但是鉤子中的setPositiveNumber是自動補單位px輸出的。

// #269
jQuery.isWindow = function( obj ) {
    /* jshint eqeqeq: false */
    return obj != null && obj == obj.window;
};

// #10696
function getWindow( elem ) {
    return jQuery.isWindow( elem ) ?
        elem :
        elem.nodeType === 9 ?
            elem.defaultView || elem.parentWindow :
            false;
}

// #10893
jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
    jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name },

    // funcName代表innerWidth、innerHeight、outerWidth、outerHeight
    function( defaultExtra, funcName ) {

        // 參數 ( [ margin [,value] ] ),margin才是數值
        // 正確使用 $().outerHeight(值, true)
        jQuery.fn[ funcName ] = function( margin, value ) {
            // defaultExtra有值,說明是padding和content的情況,只要有參數說明鏈式
            // defaultExtra為"",說明是margin和border情況,只有首參數有值並不為boolean,則鏈式
            var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
                // 確定級別
                extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );

            // 第1類access用法
            return access( this, function( elem, type, value ) {
                var doc;

                if ( jQuery.isWindow( elem ) ) {

                    // "clientHeight"  "clientWidth" 可視區大小
                    return elem.document.documentElement[ "client" + name ];
                }

                // Get document width or height
                if ( elem.nodeType === 9 ) {
                    doc = elem.documentElement;

                    // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
                    // whichever is greatest
                    // unfortunately, this causes bug #3838 in IE6/8 only,
                    // but there is currently no good, small way to fix it.
                    return Math.max(
                        elem.body[ "scroll" + name ], doc[ "scroll" + name ],
                        elem.body[ "offset" + name ], doc[ "offset" + name ],
                        doc[ "client" + name ]
                    );
                }

                // 此處value -> chainable ? margin : undefined
                return value === undefined ?

                    // extra傳遞級別,並且不強制的去除單位
                    jQuery.css( elem, type, extra ) :

                    // Set width or height on the element
                    jQuery.style( elem, type, value, extra );
            }, type, chainable ? margin : undefined, chainable, null );
        };
    } );
} );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

9、display與jQuery.fn.show/hide/toggle

與jQuery.fn.css()在一起定義的還有jQuery.fn.show/hide/toggle()方法,通過display:none影響元素的可見性。如同swap函數會記錄樣式並還原一樣,為了讓隱藏還原為可見的過程中display與隱藏前保持一致,把display的原狀態存入jQuery._data(elem, 「olddisplay」)緩存,對於沒有緩存的顯示的時候會調用defaultDisplay( nodeName )首先查詢elemdisplay列表中是否有默認值,若沒有,內部調用actualDisplay( name, doc )通過創建一個相同標籤檢測display得到需要設置的display值(結果會添加到elemdisplay列表加快以後查詢),從而保證正確的顯示狀態。這一切的核心過程就是showHide()
showHide()這個函數跟jq中很多函數一樣,很污!合併大量分支條件,就是不肯多寫兩行代碼的臭毛病,原諒我年輕,趕腳是在炫技!!
要點:
1、設置為hide,都是通過elem.style.display= none。緩存顯示狀態到_data(已經有緩存值則不需要)
2、當調整為顯示時使用緩存值。緩存值一定是最終可靠值,儘管信任
3、這種邏輯關係建立在只通過showHide()機制控制是否顯示,這樣記憶的值才能真正代表上一次
show 變量代表變為顯示狀態,hidden 變量代表本身是隱藏
show(設置顯示) -> hidden(本身隱藏) :調整為_data緩存中保存的用於顯示的設置;若無緩存,先把display設置為」「,若此時檢測仍為hidden,則設置display到元素初始默認,並緩存起來(對文檔片段也是友好的)
show(設置顯示) -> !hidden(本身顯示) :不做處理。下面會路過elem.style.display === 「」 -> values[ index ] || 「」 ,相當於沒處理
!show(設置隱藏)-> hidden(本身隱藏) :display && display !== 「none」,不為」「但仍隱藏,說明這段代碼是為了兼容文檔片段的,會緩存elem.style.display。最終display設為none
!show(設置隱藏)-> !hidden(本身顯示) :緩存css取到的生效樣式,因為style的可能為」「,最終display設為none
/* #4304 isHidden
 * 對display:none,或者文檔片段中的節點,返回true
---------------------------------------------------------------------- */
var isHidden = function( elem, el ) {
    elem = el || elem;
    return jQuery.css( elem, "display" ) === "none" ||
        !jQuery.contains( elem.ownerDocument, elem );
};

// #6427  默認display值列表
var iframe,
    elemdisplay = {

        // Support: Firefox
        // We have to pre-define these values for FF (#10227)
        HTML: "block",
        BODY: "block"
    };

/* #6443 actualDisplay
 * 創建時默認生效的display
---------------------------------------------------------------------- */
function actualDisplay( name, doc ) {
    var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),

        display = jQuery.css( elem[ 0 ], "display" );

    // #6251  刪除該節點
    elem.detach();

    return display;
}

/* #6459 defaultDisplay
 * 創建時默認生效的display
---------------------------------------------------------------------- */
function defaultDisplay( nodeName ) {
    var doc = document,
        display = elemdisplay[ nodeName ];

    if ( !display ) {
        display = actualDisplay( nodeName, doc );

        // If the simple way fails, read from inside an iframe
        // 失效了,就在一個iframe再試一次
        if ( display === "none" || !display ) {

            // Use the already-created iframe if possible
            iframe = ( iframe || jQuery( "<iframe frameborder='0' width='0' height='0'/>" ) )
                .appendTo( doc.documentElement );

            // Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse
            doc = ( iframe[ 0 ].contentWindow || iframe[ 0 ].contentDocument ).document;

            // Support: IE
            doc.write();
            doc.close();

            display = actualDisplay( nodeName, doc );
            iframe.detach();
        }

        // Store the correct default display,緩存
        elemdisplay[ nodeName ] = display;
    }

    return display;
}


/* #6878 showHide
 * show為true設置顯示,為false設置隱藏
---------------------------------------------------------------------- */
function showHide( elements, show ) {
    var display, elem, hidden,
        values = [],
        index = 0,
        length = elements.length;

    for ( ; index < length; index++ ) {
        elem = elements[ index ];
        if ( !elem.style ) {
            continue;
        }

        // 緩存值
        values[ index ] = jQuery._data( elem, "olddisplay" );
        display = elem.style.display;
        if ( show ) {

            // display === "none"是無需記憶的屬性,可以直接修改來試探
            if ( !values[ index ] && display === "none" ) {
                elem.style.display = "";
            }

            // 顯示轉隱藏時緩存才必要,這裡只是為了方便,因為要被設置為這個值,可以直接借用下面values[ index ] || ""
            if ( elem.style.display === "" && isHidden( elem ) ) {
                values[ index ] =
                    jQuery._data( elem, "olddisplay", defaultDisplay( elem.nodeName ) );
            }
        } else {
            hidden = isHidden( elem );

            // !hidden顯示轉!show隱藏需緩存,文檔片段造成的隱藏仍需緩存display && display !== "none"
            if ( display && display !== "none" || !hidden ) {
                jQuery._data(
                    elem,
                    "olddisplay",
                    hidden ? display : jQuery.css( elem, "display" )
                );
            }
        }
    }

    // 開始設置
    for ( index = 0; index < length; index++ ) {
        elem = elements[ index ];
        if ( !elem.style ) {
            continue;
        }
        if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
            elem.style.display = show ? values[ index ] || "" : "none";
        }
    }

    return elements;
}


/* #7362 show hide toggle
 * 外觀,toggle不帶參數則是設置為相反。帶參數則控制show/hide
---------------------------------------------------------------------- */
jQuery.fn.extend( {

    show: function() {
        return showHide( this, true );
    },
    hide: function() {
        return showHide( this );
    },
    toggle: function( state ) {
        if ( typeof state === "boolean" ) {
            return state ? this.show() : this.hide();
        }

        return this.each( function() {
            // 文檔片段總是隱藏,調用show
            if ( isHidden( this ) ) {
                jQuery( this ).show();
            } else {
                jQuery( this ).hide();
            }
        } );
    }
} );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155

定位

jQuery不僅能夠便利的支持快捷的盒模型設置,還提供了兼容的方式獲取定位信息。
jQuery.fn.offset()
無參數時,獲取元素到頁面左上角的距離,包括滾動條。通常通過getBoundingClientRect()獲取相對視口的(對於IE6-8左上角左邊從2,2開始算,所以要減去document.documentElement.clientTop/left),然後加上滾動距離window.pageXOffset/pageYOffset(IE低版本不支持,需要用document.documentElement.scrollTop/scrollLeft,這裡不考慮IE混雜模式),下面還實現了jQuery.fn.scrollLeft()/scrollTop()兼容的獲取或設置元素或window的滾動距離,源碼中沒調用這個方法,個人覺得有點奇怪雖然無傷大雅。
支持兩種參數形式(css獲取的position為」static」會設為」relative」),內部調用jQuery.offset.setOffset( elem, options, i )設置:
對象{left: x, top: x}直接設置
函數fn(i, options),會注入當前位置信息({left: x, top: x})到options,可在函數內修正坐標,之後options會被設置到元素
jQuery.fn.position()
讀取基於最近有定位的祖先節點或根節點的border內側的相對偏移,margin部分也算做了元素的一部分,所以偏移量不包含margin。之所以選擇包含margin,是為了css設置top和left定位元素考慮,這樣就不用擔心因為margin的影響要修正top和left了
在原生方法中有elem.offsetLeft、elem.offsetTop,代表相對最近有定位祖先的距離,但是源碼卻沒用它,反而用了更麻煩的方法獲取到元素和有定位祖先的offset()然後相減,原因應該是因為IE低版本的兩個方法有bug,獲取的是相對父集的偏移而無需有定位。
/* #10698 show hide toggle
 * document和window會返回對應的window,其他元素都返回false
---------------------------------------------------------------------- */
function getWindow( elem ) {
    return jQuery.isWindow( elem ) ?
        elem :
        elem.nodeType === 9 ?
            elem.defaultView || elem.parentWindow :
            false;
}

/* #10706 jQuery.offset.setOffset
 * jQuery.fn.offset內用於設置時調用的核心方法,設置top、left屬性
---------------------------------------------------------------------- */
jQuery.offset = {
    setOffset: function( elem, options, i ) {
        var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
            position = jQuery.css( elem, "position" ),
            curElem = jQuery( elem ),
            props = {};

        // 需要把默認變為相對定位,設置才能生效。此時top、left是以它當前位置為基準的(與absolute不同)
        if ( position === "static" ) {
            elem.style.position = "relative";
        }

        // 獲取相對頁面起始處的距離(含滾動區域)
        curOffset = curElem.offset();

        // 當前的top、left值
        curCSSTop = jQuery.css( elem, "top" );
        curCSSLeft = jQuery.css( elem, "left" );

        // relative的auto是0,而absolute與fixed的auto並不相當於0,因為它們是相對最近的有定位祖先節點或根元素的
        calculatePosition = ( position === "absolute" || position === "fixed" ) &&
            jQuery.inArray( "auto", [ curCSSTop, curCSSLeft ] ) > -1;

        // 計算
        if ( calculatePosition ) {
            // 為auto時,獲取相對最近的有定位祖先節點或根元素的距離,得到真實值,得到值無單位。因為margin值被考慮在方法中,因此獲取的top/left無需修正
            curPosition = curElem.position();
            curTop = curPosition.top;
            curLeft = curPosition.left;

        // relative無需計算,需要去除單位
        } else {
            curTop = parseFloat( curCSSTop ) || 0;
            curLeft = parseFloat( curCSSLeft ) || 0;
        }

        // options為函數時,調用得到修正後需要設置的offset()坐標,並賦值給options
        if ( jQuery.isFunction( options ) ) {

            // fn(i, options)  可自定義修正坐標
            options = options.call( elem, i, jQuery.extend( {}, curOffset ) );
        }

        // 相對之前offset()增加的值,就是需要在top\left屬性上追加的值
        if ( options.top != null ) {
            props.top = ( options.top - curOffset.top ) + curTop;
        }
        if ( options.left != null ) {
            props.left = ( options.left - curOffset.left ) + curLeft;
        }

        // 可以通過using屬性定義鉤子函數,取代默認的寫入
        if ( "using" in options ) {
            options.using.call( elem, props );
        } else {
            // 默認直接通過{top: x, left: x}對象形式調用css寫入
            curElem.css( props );
        }
    }
};

jQuery.fn.extend( {

/* #10757 jQuery.fn.offset
 * 相對頁面(含滾動區)的坐標對象{left,top},可讀可寫,參數可為函數
---------------------------------------------------------------------- */
    offset: function( options ) {
        if ( arguments.length ) {
            return options === undefined ?
                this :
                // 對所有元素設置
                this.each( function( i ) {
                    jQuery.offset.setOffset( this, options, i );
                } );
        }

        var docElem, win,
            box = { top: 0, left: 0 },
            elem = this[ 0 ],
            doc = elem && elem.ownerDocument;

        if ( !doc ) {
            return;
        }

        docElem = doc.documentElement;

        // 文檔片段元素按照{left:0, top:0}返回
        if ( !jQuery.contains( docElem, elem ) ) {
            return box;
        }

        // If we don't have gBCR, just use 0,0 rather than error
        // BlackBerry 5, iOS 3 (original iPhone)
        if ( typeof elem.getBoundingClientRect !== "undefined" ) {
            // 得到相對視口的坐標
            box = elem.getBoundingClientRect();
        }
        win = getWindow( doc );
        return {
            // + 滾動距離 - 低版本IE的(2,2)修正
            top: box.top  + ( win.pageYOffset || docElem.scrollTop )  - ( docElem.clientTop  || 0 ),
            left: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 )
        };
    },

/* #10794 jQuery.fn.position
 * 相對最近的有定位祖先的位置,margin範圍算作元素的一部分
---------------------------------------------------------------------- */
    position: function() {
        if ( !this[ 0 ] ) {
            return;
        }

        var offsetParent, offset,
            parentOffset = { top: 0, left: 0 },
            elem = this[ 0 ];

        // Fixed elements are offset from window (parentOffset = {top:0, left: 0},
        // because it is its only offset parent
        if ( jQuery.css( elem, "position" ) === "fixed" ) {

            // fixed的top和left是以視口為基準,直接取坐標
            offset = elem.getBoundingClientRect();
        } else {

            // Get *real* offsetParent
            // offsetParent()方法會把冒泡到body節點的(且body也無定位)按照html節點處理
            offsetParent = this.offsetParent();

            // Get correct offsets
            // 得到自身和offsetParent的offset()
            offset = this.offset();
            if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
                parentOffset = offsetParent.offset();
            }

            // Add offsetParent borders
            // Subtract offsetParent scroll positions

            // 修正,因為是到offsetParent的border內側
            //如果元素本身帶滾動條,並且滾動了一段距離,那麼兩者間實際的偏移應該更多
            parentOffset.top += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true ) -
                offsetParent.scrollTop();
            parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true ) -
                offsetParent.scrollLeft();
        }

        // 兩offset()相減,margin需作為元素一部分,去掉
        return {
            top:  offset.top  - parentOffset.top - jQuery.css( elem, "marginTop", true ),
            left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
        };
    },

/* #10837 jQuery.fn.offsetParent
 * 獲得元素集合對應的offsetParent(或html元素)集合
---------------------------------------------------------------------- */
    offsetParent: function() {
        return this.map( function() {
            // 最近的有定位的上層元素
            var offsetParent = this.offsetParent;

            // display:none或position:fixed為null。正常元素若找不到則向上到body返回
            while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) &&
                jQuery.css( offsetParent, "position" ) === "static" ) ) {
                offsetParent = offsetParent.offsetParent;
            }
            return offsetParent || documentElement;
        } );
    }
} );

/* #10851 jQuery.fn.scrollLeft/scrollTop
 * 滾動距離
---------------------------------------------------------------------- */
jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
    var top = /Y/.test( prop );

    jQuery.fn[ method ] = function( val ) {
        return access( this, function( elem, method, val ) {
            // window有單獨的推薦方法,所以先識別是否為window或document
            var win = getWindow( elem );

            if ( val === undefined ) {
                // window,並且兼容pageXOffset,則使用推薦的window.pageXOffset
                // 否則使用元素的scrollLeft/scrollTop方法,window則使用根元素的該方法
                return win ? ( prop in win ) ? win[ prop ] :
                    win.document.documentElement[ method ] :
                    elem[ method ];
            }

            // window.scrollTo(xpos,ypos),對於不需改變的那個使用原值jQuery( win ).scrollxxx()
            if ( win ) {
                win.scrollTo(
                    !top ? val : jQuery( win ).scrollLeft(),
                    top ? val : jQuery( win ).scrollTop()
                );

            } else {
                // elem.scrollLeft/scrollTop = xxx;
                elem[ method ] = val;
            }
        }, method, val, arguments.length, null );
    };
} );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
1
 
0