DOM 尺寸 API:offset / scroll / client
JavaScript 里关于元素尺寸的 API 有三组共九个:offsetWidth / offsetHeight、scrollWidth / scrollHeight、clientWidth / clientHeight,加上对应的 Top / Left。看起来一样,实际差别巨大。本文系统梳理一遍,带可视化图。



1. offset 系列(元素本身)
offsetWidth / offsetHeight 表示元素实际占据的尺寸,包括:
- content 宽 / 高
- padding(上下左右)
- border(上下左右)
- 滚动条(如果有的话,scrollbar 也算在内)
不包括:margin、被 overflow:hidden 隐藏掉的内容。
计算公式:
offsetWidth = 左 border + 左 padding + content 宽 + 右 padding + 右 border
offsetHeight = 上 border + 上 padding + content 高 + 下 padding + 下 border
offsetParent
element.offsetParent 返回当前元素的"最近的已定位祖先元素"(position: relative / absolute / fixed)。如果没有这样的祖先,返回 body。
规则:
- 当前元素没有任何已定位祖先 →
offsetParent = body - 有已定位祖先 →
offsetParent = 最近的那个 - 元素本身
display: none→offsetParent = null
offsetTop / offsetLeft
相对于 offsetParent 的偏移量。注意是相对于 offsetParent,不是相对于 viewport。
const box = document.querySelector('.box');
console.log(box.offsetTop, box.offsetLeft);
// 相对于 box.offsetParent 的位置
计算公式:
offsetTop = offsetParent 的上 padding
+ 当前元素之前所有兄弟元素的 offsetHeight 之和
+ 当前元素的上 margin
2. scroll 系列(包括滚动隐藏部分)
scrollWidth / scrollHeight 表示元素的完整内容尺寸(包括被滚动条隐藏的部分)。
- 如果元素内容没溢出:
scrollWidth === clientWidth - 如果内容溢出(出现滚动条):
scrollWidth > clientWidth,等于实际内容的总宽
例子:一个 200px 宽的 div 里塞了 800px 宽的内容(overflow: auto),那:
- offsetWidth = 200(元素自身)
- clientWidth = 200 - 滚动条宽度(可视区域)
- scrollWidth = 800(完整内容)
scrollTop / scrollLeft
元素的滚动距离 —— 内容相对于可视区域顶部 / 左侧的偏移。可读可写:
// 读:获取当前滚动位置
const scrollY = element.scrollTop;
// 写:滚动到指定位置
element.scrollTop = 100;
// 平滑滚动(现代浏览器)
element.scrollTo({ top: 100, behavior: 'smooth' });
3. client 系列(可视区域,不含滚动条)
clientWidth / clientHeight 是可视区域的尺寸 —— content + padding,不含 border、margin、滚动条。
计算公式:
clientWidth = 左 padding + content 宽 + 右 padding (无 border 无滚动条)
clientHeight = 上 padding + content 高 + 下 padding
clientTop / clientLeft
这俩名字误导大 —— 不是位置,而是 border 的宽度:
element.clientTop // 上 border 宽度
element.clientLeft // 左 border 宽度
对比表
| API | 包含 padding | 包含 border | 包含 overflow 内容 | 包含滚动条 |
|---|---|---|---|---|
| offsetWidth / Height | ✓ | ✓ | ✗ | ✓ |
| scrollWidth / Height | ✓ | ✗ | ✓ | ✗ |
| clientWidth / Height | ✓ | ✗ | ✗ | ✗ |
实际应用场景
1. 判断元素是否在 viewport 内(用 getBoundingClientRect 更现代,但 offset 也能):
function isInViewport(el) {
const rect = el.getBoundingClientRect();
return rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= window.innerHeight &&
rect.right <= window.innerWidth;
}
2. 检测元素是否有滚动条:
function hasScroll(el) {
return el.scrollHeight > el.clientHeight ||
el.scrollWidth > el.clientWidth;
}
3. 监听滚动到底部(无限滚动加载):
window.addEventListener('scroll', () => {
const scrollTop = window.scrollY || document.documentElement.scrollTop;
const clientHeight = document.documentElement.clientHeight;
const scrollHeight = document.documentElement.scrollHeight;
if (scrollTop + clientHeight >= scrollHeight - 100) {
// 距底部 100px 时触发加载下一页
loadMore();
}
});
更现代的方法用 IntersectionObserver:
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) loadMore();
});
observer.observe(document.querySelector('.load-trigger'));
getBoundingClientRect (推荐用这个)
实际项目里,我更推荐 getBoundingClientRect(),它返回的 DOMRect 对象有 top / right / bottom / left / width / height / x / y 全套信息,且都相对于 viewport(不是 offsetParent),计算更直观:
const rect = element.getBoundingClientRect();
console.log(rect.top, rect.left, rect.width, rect.height);
// 相对 viewport 的位置和尺寸,小数精度
offset 系列保留下来主要是历史包袱,getBoundingClientRect 是更现代的选择。
一句话总结
offset*—— 元素自身完整尺寸(含 border)和相对 offsetParent 的位置scroll*—— 完整内容尺寸(含被滚动隐藏的)和当前滚动距离client*—— 可视内容区域(不含 border、滚动条)- 新代码优先用
getBoundingClientRect,简单清晰
—— 别看了 · 2026