Js位置與大小(1)——正確理解和運用與尺寸大小相關的DOM屬性

在web開發中,不可避免遇到要計算元素大小以及位置的問題,解決這類問題的方法是利用DOM提供的一些API結合兼容性處理來,全部內容大概分3篇左右的文章的來講明。本文做爲第一篇,介紹DOM提供的與尺寸大小相關的DOM屬性,提供一些兼容性處理的方法,並結合常見的場景說明如何正確運用這些屬性。javascript

1. 正確理解offsetWidth、clientWidth、scrollWidth及相應的height屬性

假設某一個元素的橫縱向滾動條都拖動到最末端,則offsetWidth、clientWidth、scrollWidth等屬性相應的範圍以下圖所示:css

另外還有一個代碼比較形象的展現了這幾個屬性:http://jsfiddle.net/y8Y32/25/html

1)offsetWidth ,offsetHeight對應的是盒模型的寬度和高度,這兩個值跟咱們使用chrome審查元素時看到的尺寸一致:html5

image

2)scrollWidth,與scrollHeight對應的是滾動區域的寬度和高度可是不包含滾動條的寬度!滾動區域由padding和content組成。java

3)clientWidth,clientHeight對應的是盒模型除去邊框後的那部分區域的寬度和高度,不包含滾動條的寬度。jquery

4)任何一個DOM元素,均可以經過如下api快速獲得offsetWidth,clientWidth,scrollWidh及相關的height屬性:web

//domE爲一個DOM Html Element對象
domE.scrollWidth
domE.scrollHeight
domE.clientWidth
domE.clientHeight
domE.offsetWidth
domE.offsetHeight

5)這些屬性在現代瀏覽器包括pc和mobile上幾乎沒有兼容性問題,能夠放心使用。若是你想了解詳細的兼容性規則,能夠參考下面的2篇文章:chrome

W3C DOM Compatibility - CSS Object Model Viewapi

cssom視圖模式cssom-view-module相關整理與介紹瀏覽器

下面針對普通html元素,html根元素和body元素的以上相關屬性一一測試,以便驗證前面的結論,總結一些可在實際編碼過程當中直接使用的經驗技巧。之因此要區分普通html元素,html根元素和body元素,是由於前面的理論,在html根元素和body元素會有一些怪異之處,須要當心處理。

注:

一、爲了減小篇幅,測試貼出的代碼不是完整的代碼,但不影響學習參考,另外文中給出的測試結果都是在chrome(版本:45.0)下運行得出的,在測試結果有差別的狀況下,還會給出IE9,IE10,IE11,firefox(版本:42.0),opera(版本:34.0)的測試結果,沒有差別的會在測試結果中說明,不考慮IE8及如下。

二、safari由於設備限制暫不測試,另外它跟chrome內核相同,對標準支持的可靠性差不到哪去。

三、老版本的chrome,firefox,opera也由於設備的限制沒法測試,不過從瀏覽器對標準的支持程度考慮,這三個瀏覽器在很早的版本開始對W3C的標準都是比較規矩的,加之這些瀏覽器更新換代的速度較快,如今市面上這些瀏覽器主流的版本也都是較新的。

四、因爲不考慮IE8及如下,同時html如今都用html5,因此document.compatMode = 'BackCompat' 的狀況不考慮。不過儘管BackCompat模式是IE6類的瀏覽器引出的,可是對於chrome,firefox等也存在document.compatMode = 'BackCompat' 的狀況,好比下面的這個網頁,你用chrome打開,而且在console中打印document.compatMode,你會發現它的值也是BackCompat(緣由跟該頁面用的是html4.0的dtd有關,若是換成html4.01的dtd就不會在chrome和firefox裏出現該狀況了):
http://samples.msdn.microsoft.com/workshop/samples/author/dhtml/refs/compatModeCompat.htm
更多關於compatMode的知識,你能夠經過下面的幾個資源學習:
https://developer.mozilla.org/zh-CN/docs/Web/API/Document/compatMode
https://msdn.microsoft.com/en-us/library/ms533687(VS.85).aspx
http://www.cnblogs.com/uedt/archive/2010/09/21/1832402.html

 

測試1、驗證普通html元素(非body及html根元素)的offsetWidth、clientWidth、scrollWidth及相關height屬性:

<style type="text/css">
    html,
    body {
        margin: 0;
    }
    body {
        padding: 100px;
    }
    .box {
        overflow: scroll;
        width: 400px;
        height: 300px;
        padding: 20px;
        border: 10px solid #000;
        margin: 0 auto;
        box-sizing: content-box;
    }
    .box-2 {
        border: 1px solid #000;
    }
</style>
<body>
    <div class="box">
        <div class="box-2">...</div>
    </div>
</body>
<script type="text/javascript">
var boxE = document.querySelectorAll('.box')[0];
console.log('scrollWidth:' + boxE.scrollWidth);
console.log('scrollHeight:' + boxE.scrollHeight);
console.log('clientWidth:' + boxE.clientWidth);
console.log('clientHeight:' + boxE.clientHeight);
console.log('offsetWidth :' + boxE.offsetWidth);
console.log('offsetHeight:' + boxE.offsetHeight);
</script>

在這個例子中,box元素有400*300的寬高,20px的padding和10px的border,chrome下對應的盒模型:

image

image

js執行結果:

image

從盒模型與js執行結果可知:

1)offsetWidth與offsetHeight與chrome審查元素看到的尺寸徹底一致;

2)clientWidth與clientHeight分別等於offsetWidth與offsetHeight減掉相應邊框(上下共20px,左右共20px)和滾動條寬度後的值(chrome下滾動條寬度爲17px);

3)對於scrollWidth因爲沒有發生橫向的溢出,同時因爲overflow: scroll的緣由,scrollWidth 跟clientWidth相同,可是沒有包含滾動條的寬度,這也驗證了前面提出的結論;

4)對於scrollHeight,在這個例子中,它其實等於上下padding(共40px) + div.box-2的offsetHeight(1370px),div.box-2:

image

5)以上測試還有一個css值得注意,就是box-sizing,以上代碼中box-sizing設置爲了content-box,若是把它改爲border-box,結果也是相似的,由於offsetWidth,clientWidth還有scrollWidth對應的區域不會發生改變。

6)其它瀏覽器運行結果與1-5的結論一致。

 

測試2、驗證html根元素和body元素的相關offset client scroll寬高屬性:

<style type="text/css">
    html,
    body {
        margin: 0;
    }
    
    body {
        border: 10px solid #D4D2D2;
    }
    
    .box {
        overflow: scroll;
        width: 400px;
        height: 300px;
        padding: 20px;
        border: 10px solid #000;
        margin: 0 auto;
        box-sizing: content-box;
    }
    
    .box-2 {
        border: 1px solid #000;
    }
</style>
<body>
    <div class="box">
        <div class="box-2">...</div>
    </div>
    <div class="box">
        <div class="box-2">...</div>
    </div>
    <div class="box">
        <div class="box-2">...</div>
    </div>
    <div class="box">
        <div class="box-2">...</div>
    </div>
</body>
<script>
console.log('docE.scrollWidth:' + document.documentElement.scrollWidth);
console.log('scrollHeight:' + document.documentElement.scrollHeight);
console.log('docE.clientWidth:' + document.documentElement.clientWidth);
console.log('docE.clientHeight:' + document.documentElement.clientHeight);
console.log('docE.offsetWidth :' + document.documentElement.offsetWidth);
console.log('docE.offsetHeight:' + document.documentElement.offsetHeight);

console.log('');

console.log('body.scrollWidth:' + document.body.scrollWidth);
console.log('body.scrollHeight:' + document.body.scrollHeight);
console.log('body.clientWidth:' + document.body.clientWidth);
console.log('body.clientHeight:' + document.body.clientHeight);
console.log('body.offsetWidth :' + document.body.offsetWidth);
console.log('body.offsetHeight:' + document.body.offsetHeight);
</script>

在這個例子中,body下一共有4個box元素(總高度爲360 * 4 = 1440px),body的寬是自適應的,body還有10px的border,運行結果以下:

image

image

從這個結果能夠看到:

1)body元素因爲10px邊框的緣由,因此clientWidth比offsetWidth少了20px,這跟前面提到的理論是一致的,可是難以想象的是body的scrollWidth/scrollHeight居然等於它的offsetWidth/offsetHeight,scrollWidth/scrollHeight是元素滾動區域的寬高度,按照前面給出的範圍圖來理解,在body沒有出現滾動條的時候,body的scrollWidth/scrollHeight應該小於它的offsetWidth/offsetHeight纔對;

2)docE的scrollWidth跟scrollHeight,應該等於body元素的offsetWidth跟offsetHeight,從運行結果來看,這一點是符合的,可是docE的clientWidth居然等於它的offsetWidth,按照範圍圖,docE的clientWidth應該等於offsetWidth減去滾動條寬度纔對。

其它的瀏覽器運行結果與chrome也有較大的差別:

IE11

image

1)IE11下body元素沒有出現chrome下body元素的問題

2)IE11下html根元素也有chrome相似的問題

IE10,IE9:

image

1)IE10,9下body元素沒有出現chrome下body元素的問題

2)IE10,9下html根元素也沒有chrome相似的問題

firefox:與IE11運行結果一致。

opera: 與chrome運行結果一致,多是由於我這個版本的opera用的跟chrome同樣的webkit內核的緣由。

 

看起來IE9就跟IE10是最正常的,實在是有點難以理解,網上搜索好久,也沒有找到相關資料來講明這些差別,最後也只能採起大膽假設的方式,猜想出幾個能解釋這些問題的緣由

1) 首先,網頁總體的滾動,跟普通html元素的滾動不同,普通html元素自身就是滾動對象, 可是對於網頁來講,滾動對象不必定是html根元素或者body元素。由於當body內容爲空時,body的高度是0,html根元素的高度也是0,若是這個時候給html或body加上overflow: scroll的css,會看到滾動條仍是出現瀏覽器窗口的右邊跟底邊,因此對於網頁總體的滾動,理論上,滾動對象應該是window,而不是html元素或者body元素!但實際狀況並不是如此,就測試的瀏覽器而言:

對於IE10,IE9,它的滾動對象是html根元素,因此它們的html根元素的offset會包含滾動條的寬度;

對於其它瀏覽器,滾動對象是window,因此它們的html根元素的offset不包含滾動條的寬度。

2)第二,普通元素髮生滾動時,滾動內容=它的content區域+它的padding區域,當網頁總體滾動時,滾動內容應該是html根元素!但實際狀況也並不是如此,就測試的瀏覽器而言:

對於IE9,IE10,IE11,firefox,它們的滾動區域是html根元素,因此它們的documentElement的scrollWidth和scrollHeight始終表示網頁總體的滾動區域大小!

對於chrome和opera,它們的滾動對象是body元素,因此它們的body的scrollWidth和scrollHeight始終表示網頁總體的滾動區域大小!

3)第三,瀏覽器始終把documentElement.clientWidth和documentElement.clientHeight描述爲網頁可視區域除去滾動條部分的大小,跟網頁內容沒有關係!

以上的這些推斷也並不是是毫無道理,就拿滾動對象和滾動區域來講:chrome下若是要用js滾動頁面到某個位置,在不使用window.scrollTo的條件下,就必須用document.body.scrollTop = xxx 來處理,而設置document.documentElement.scrollTop無效,說明chrome的總體滾動區域是由body的滾動區域決定的;而IE11和火狐下若是要用js滾動頁面到某個位置,在不使用window.scrollTo的條件下,就必須用document.documentElement.scrollTop = xxx來處理,設置document.body.scrollTop無效,說明IE11和火狐的總體滾動區域是由html根元素的滾動區域決定的。

2. 利用JS準確獲取DOM對象的大小

常見的場景有:

1)獲取整個網頁的可視區域的大小,不包括滾動條

2)獲取整個網頁的大小,包括不可見的滾動區域,不包括滾動條

3)獲取一個普通html元素的大小

4)判斷元素或網頁有無出現滾動條

5)計算滾動條的寬度

下面針對這5個場景一一說明,如下代碼均不考慮IE8及如下,不考慮html4,另外請注意viewport的設置,要保證在移動設備上visual viewport與layout viewport重合。

1)如何獲取整個網頁的可視區域的大小,不包括滾動條

document.documentElement.clientWidth;
document.documentElement.clientHeight;

2)如何獲取整個網頁的大小,包括不可見的滾動區域,不包括滾動條

function pageWidth() {
    var doc = document.documentElement,
        body = document.body;

    return Math.max(
        body["scrollWidth"], doc["scrollWidth"], doc["offsetWidth"], 
        body["offsetWidth"], doc["clientWidth"]
    );
}

function pageHeight() {
    var doc = document.documentElement,
        body = document.body;

    return Math.max(
        body["scrollHeight"], doc["scrollHeight"], doc["offsetHeight"], 
        body["offsetHeight"], doc["clientHeight"]
    );
}

以上代碼從jquery中摘出。

3)如何獲取一個普通html元素的大小

簡單方法:

docE.offsetWidth;
docE.offsetHeight;

利用getBoundingClientRect:

var obj = docE.getBoundingClientRect(),
    elemWidth,
    elemHeight;

if(obj) {
    if(obj.width) {
        elemWidth = obj.width;
        elemHeight = obj.height;
    } else {
        elemWidth = obj.right - obj.left;
        elemHeight = obj.bottom - obj.top;
    }
} else {
    elemWidth = docE.offsetWidth;
    elemHeight = docE.offsetHeight;
}

getBoundingClientRect將在下篇文章中跟其它與位置有關的DOM屬性一塊兒再詳細介紹。

4)判斷元素或網頁有無出現滾動條

function scrollbarState(elem) {
    var docE = document.documentElement,
        body = document.body;
    if (!elem || elem === document || elem === docE || elem === body) {
        return {
            scrollbarX: docE.clientHeight < window.innerHeight,
            scrollbarY: docE.clientWidth < window.innerWidth
        }
    }
    if (typeof(Element) == 'function' && !(elem instanceof(Element) || !body.contains(elem))) {
        return {
            scrollbarX: false,
            scrollbarY: false
        };
    }

    var elemStyle = elem.style,
        overflowStyle = {
            hidden: elemStyle.overflow == 'hidden',
            hiddenX: elemStyle.overflowX == 'hidden',
            hiddenY: elemStyle.overflowY == 'hidden',
            scroll: elemStyle.overflow == 'scroll',
            scrollX: elemStyle.overflowX == 'scroll',
            scrollY: elemStyle.overflowY == 'scroll'
        };
    return {
        scrollbarX: overflowStyle.scroll || overflowStyle.scrollX || (!overflowStyle.hidden && !overflowStyle.hiddenX && elem.clientWidth < elem.scrollWidth),
        scrollbarY: overflowStyle.scroll || overflowStyle.scrollY || (!overflowStyle.hidden && !overflowStyle.hiddenY && elem.clientHeight < elem.scrollHeight)
    };
}

當x或y方向的overflow爲scroll的時候,該方向的scrollbarX爲true,表示出現滾動條。

5)計算滾動條的寬度

function scrollbarWidth() {
    var docE = document.documentElement,
        body = document.body,
        e = document.createElement('div');

    e.style.cssText = 'position: absolute; top: -9999px; width: 50px; height: 50px; overflow: scroll;';

    body.appendChild(e);
    var _scrollbarWidth = e.offsetWidth - e.clientWidth
    body.removeChild(e);
    return _scrollbarWidth;
}

以上就是本文的所有內容,但願能對您有所幫助:)另外本文第二部分提供的代碼,是根據我的思考和經驗總結出的一些方法,在兼容性方面可能還有未考慮到的地方,若是您有遇到其它不兼容的狀況或者有更好的代碼,還請不吝賜教,歡迎您的指導。

謝謝閱讀:)

相關文章
相關標籤/搜索