JavaScript DOM尺寸与位置:offsetWidth、clientWidth与scrollWidth详解及兼容处理

作者:微光时间:2026-10-29 19:21:17浏览:492

本文深入解析JavaScript中offsetWidth、clientWidth和scrollWidth等DOM尺寸属性的定义与区别,通过测试验证不同浏览器(Chrome、IE、Firefox等)对html根元素和body元素的渲染差异。文章提供获取网页可视区域、计算滚动条宽度及判断滚动条存在的实用代码方案,并针对跨浏览器兼容性问题给出具体处理建议。

引言与DOM尺寸属性概述

在Web开发中,计算元素的大小和位置是常见需求。解决此类问题通常依赖DOM提供的API,并辅以兼容性处理。本系列文章计划分为三篇,本文作为第一篇,重点介绍与尺寸大小相关的DOM属性,提供兼容性处理方法,并结合常见场景说明如何正确运用这些属性。

offsetWidth、clientWidth与scrollWidth详解

1. 正确理解offsetWidth、clientWidth、scrollWidth及相应的height属性

假设某一元素的横向和纵向滚动条均拖动至最末端,offsetWidth、clientWidth、scrollWidth等属性所覆盖的范围如下图所示:

引言与DOM尺寸属性概述 

1)offsetWidth与offsetHeight对应的是盒模型的宽度和高度,这两个值与使用Chrome浏览器审查元素时看到的尺寸一致:

引言与DOM尺寸属性概述 

2)scrollWidth与scrollHeight对应的是滚动区域的宽度和高度,但不包含滚动条的宽度。滚动区域由padding和content组成。

3)clientWidth与clientHeight对应的是盒模型除去边框后的区域宽度和高度,同样不包含滚动条的宽度。

4)任何DOM元素均可通过以下API快速获取offsetWidth、clientWidth、scrollWidth及相关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和移动端)中几乎不存在兼容性问题,可以放心使用。如需了解详细的兼容性规则,可参考以下两篇文章:

W3C DOM Compatibility – CSS Object Model View

cssom视图模式cssom-view-module相关整理与介绍

接下来,针对普通HTML元素、HTML根元素(html)和body元素的上述相关属性进行逐一测试,以验证前述结论,并总结可在实际编码中直接使用的经验技巧。区分普通元素与根元素/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及以下,且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元素尺寸属性测试

测试一、验证普通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>
<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,padding为20px,border为10px,Chrome下对应的盒模型:

引言与DOM尺寸属性概述

引言与DOM尺寸属性概述

JS执行结果:

引言与DOM尺寸属性概述 

从盒模型与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:

引言与DOM尺寸属性概述 

5)以上测试还有一个CSS值得注意,即box-sizing,以上代码中box-sizing设置为content-box,若将其改为border-box,结果类似,因为offsetWidth、clientWidth和scrollWidth对应的区域不会发生改变。

6)其它浏览器运行结果与1-5的结论一致。

HTML根元素与Body元素的兼容性问题

测试二、验证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>
<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,运行结果如下:

引言与DOM尺寸属性概述

引言与DOM尺寸属性概述

从该结果可以看到:

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:

引言与DOM尺寸属性概述 

1)IE11下body元素未出现Chrome下body元素的问题

2)IE11下html根元素也有Chrome类似的问题

IE10,IE9:

引言与DOM尺寸属性概述 

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和Firefox下若要用JS滚动页面至某位置,不使用window.scrollTo时,必须用document.documentElement.scrollTop = xxx处理,设置document.body.scrollTop无效,说明IE11和Firefox的整体滚动区域由html根元素的滚动区域决定。

利用JS获取DOM对象大小的实用场景

2. 利用JS准确获取DOM对象的大小

常见场景有:

1)获取整个网页的可视区域大小,不包括滚动条

2)获取整个网页的大小,包括不可见的滚动区域

3)获取一个普通HTML元素的大小

4)判断元素或网页有无出现滚动条

5)计算滚动条的宽度

下面针对这5个场景逐一说明。以下代码均不考虑IE8及以下,不考虑HTML4,另外请注意viewport的设置,要保证在移动设备上visual viewport与layout viewport重合。

1)如何获取整个网页的可视区域大小,不包括滚动条


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

2)如何获取整个网页的大小,包括不可见的滚动区域


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元素的大小

简单方法:


docE.offsetWidth;
docE.offsetHeight;
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;
}
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)
  };
}
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)计算滚动条的宽度


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;
}

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

作者最新文章