dom节点的尺寸属性

在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);
   return _scrollbarWidth;
}

以上就是本文的全部内容,希望能对您有所帮助:)另外本文第二部分提供的代码,是根据个人思考和经验总结出的一些方法,在兼容性方面可能还有未考虑到的地方, 如果您有遇到其它不兼容的情况或者有更好的代码,还请不吝赐教 ,欢迎您的指导。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值