在web開發中,不可避免遇到要計算元素大小以及位置的問題,解決這類問題的方法是利用DOM提供的一些API結合兼容性處理來,所有內容大概分3篇左右的文章的來說明。本文作爲第一篇,介紹DOM提供的與尺寸大小相關的DOM屬性,提供一些兼容性處理的方法,並結合常見的場景說明如何正確運用這些屬性。
1. 正確理解offsetWidth、clientWidth、scrollWidth及相應的height屬性
假設某一個元素的橫縱向滾動條都拖動到最末端,則offsetWidth、clientWidth、scrollWidth等屬性相應的範圍如下圖所示:
1)offsetWidth ,offsetHeight對應的是盒模型的寬度和高度,這兩個值跟我們使用chrome審查元素時看到的尺寸一致:
2)scrollWidth,與scrollHeight對應的是滾動區域的寬度和高度 , 但是不包含滾動條的寬度!滾動區域由padding和content組成。
3)clientWidth,clientHeight對應的是盒模型除去邊框後的那部分區域的寬度和高度,不包含滾動條的寬度。
4)任何一個DOM元素,都可以通過以下api快速得到offsetWidth,clientWidth,scrollWidh及相關的height屬性:
//domE爲一個DOM Html Element對象
domE.scrollWidth
domE.scrollHeight
domE.clientWidth
domE.clientHeight
domE.offsetWidth
domE.offsetHeight
//domE爲一個DOM Html Element對象
domE.scrollWidth
domE.scrollHeight
domE.clientWidth
domE.clientHeight
domE.offsetWidth
domE.offsetHeight
5) 這些屬性在現代瀏覽器包括pc和mobile上幾乎沒有兼容性問題,可以放心使用 。如果你想了解詳細的兼容性規則,可以參考下面的2篇文章:
W3C DOM Compatibility – CSS Object Model View
cssom視圖模式cssom-view-module相關整理與介紹
下面針對普通html元素,html根元素和body元素的以上相關屬性一一測試,以便驗證前面的結論,總結一些可在實際編碼過程中直接使用的經驗技巧。之所以要區分普通html元素,html根元素和body元素,是因爲前面的理論,在html根元素和body元素會有一些怪異之處,需要小心處理。
注:
1、爲了減少篇幅,測試貼出的代碼不是完整的代碼,但不影響學習參考,另外文中給出的測試結果都是在chrome(版本:45.0)下運行得出的,在測試結果有差異的情況下,還會給出IE9,IE10,IE11,firefox(版本:42.0),opera(版本:34.0)的測試結果,沒有差異的會在測試結果中說明,不考慮IE8及以下。
2、safari因爲設備限制暫不測試,另外它跟chrome內核相同,對標準支持的可靠性差不到哪去。
3、老版本的chrome,firefox,opera也因爲設備的限制無法測試,不過從瀏覽器對標準的支持程度考慮,這三個瀏覽器在很早的版本開始對W3C的標準都是比較規矩的,加之這些瀏覽器更新換代的速度較快,現在市面上這些瀏覽器主流的版本也都是較新的。
4、由於不考慮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
測試一、驗證普通html元素(非body及html根元素)的offsetWidth、clientWidth、scrollWidth及相關height屬性:
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
|
<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>
<styletype=
"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>
<divclass=
"box"
>
<divclass=
"box-2"
>...</div>
</div>
</body>
<scripttype=
"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下對應的盒模型:
js執行結果:
從盒模型與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:
5)以上測試還有一個css值得注意,就是box-sizing,以上代碼中box-sizing設置爲了content-box,如果把它改成border-box,結果也是類似的,因爲offsetWidth,clientWidth還有scrollWidth對應的區域不會發生改變。
6)其它瀏覽器運行結果與1-5的結論一致。
測試二、驗證html根元素和body元素的相關offset client scroll寬高屬性:
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
|
<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>
<styletype=
"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>
<divclass=
"box"
>
<divclass=
"box-2"
>...</div>
</div>
<divclass=
"box"
>
<divclass=
"box-2"
>...</div>
</div>
<divclass=
"box"
>
<divclass=
"box-2"
>...</div>
</div>
<divclass=
"box"
>
<divclass=
"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,運行結果如下:
從這個結果可以看到:
1)body元素由於10px邊框的原因,所以clientWidth比offsetWidth少了20px,這跟前面提到的理論是一致的,但是不可思議的是body的scrollWidth/scrollHeight竟然等於它的offsetWidth/offsetHeight,scrollWidth/scrollHeight是元素滾動區域的寬高度,按照前面給出的範圍圖來理解,body的scrollWidth/scrollHeight應該小於它的offsetWidth/offsetHeight纔對;
2)docE的scrollWidth跟scrollHeight,應該等於body元素的offsetWidth跟offsetHeight,從運行結果來看,這一點是符合的,但是docE的clientWidth竟然等於它的offsetWidth,按照範圍圖,docE的clientWidth應該等於offsetWidth減去滾動條寬度纔對。
其它的瀏覽器運行結果與chrome也有較大的差異:
IE11:
1)IE11下body元素沒有出現chrome下body元素的問題
2)IE11下html根元素也有chrome類似的問題
IE10,IE9:
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)如何獲取整個網頁的可視區域的大小,不包括滾動條
1
2
3
4
|
document.documentElement.clientWidth;
document.documentElement.clientHeight;
document.documentElement.clientWidth;
document.documentElement.clientHeight;
|
2)如何獲取整個網頁的大小,包括不可見的滾動區域
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
|
function
pageWidth() {
var
doc = document.documentElement,
body = document.body;
if
(doc.clientWidth == window.innerWidth) {
return
doc[
"clientWidth"
];
}
return
Math.max(
body[
"scrollWidth"
], doc[
"scrollWidth"
],
body[
"offsetWidth"
], doc[
"clientWidth"
]
);
}
function
pageHeight() {
var
doc = document.documentElement,
body = document.body;
if
(doc.clientHeight == window.innerHeight) {
return
doc[
"clientHeight"
];
}
return
Math.max(
body[
"scrollHeight"
], doc[
"scrollHeight"
],
body[
"offsetHeight"
], doc[
"clientHeight"
]
);
}
function
pageWidth() {
var
doc = document.documentElement,
body = document.body;
if
(doc.clientWidth == window.innerWidth) {
return
doc[
"clientWidth"
];
}
return
Math.max(
body[
"scrollWidth"
], doc[
"scrollWidth"
],
body[
"offsetWidth"
], doc[
"clientWidth"
]
);
}
function
pageHeight() {
var
doc = document.documentElement,
body = document.body;
if
(doc.clientHeight == window.innerHeight) {
return
doc[
"clientHeight"
];
}
return
Math.max(
body[
"scrollHeight"
], doc[
"scrollHeight"
],
body[
"offsetHeight"
], doc[
"clientHeight"
]
);
}
|
以上出現的window.innerWidth和window.innerHeight分別用來獲取網頁包括滾動條的可視區域的寬高,這也是一個兼容性不錯的方法,不過從實際開發情況來看,我們需要不包括滾動條的可視區域更多一些,所以在前面沒有單獨介紹。另外在之前給出的PPK的博客中也有關於這兩個屬性的兼容性測試,可以去了解。
3)如何獲取一個普通html元素的大小
簡單方法:
1
2
3
4
|
docE.offsetWidth;
docE.offsetHeight;
docE.offsetWidth;
docE.offsetHeight;
|
利用getBoundingClientRect:
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
|
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;
}
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 )判斷元素或網頁有無出現滾動條
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
|
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)
};
}
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'
& !(eleminstanceof(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)計算滾動條的寬度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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;
}
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);
|