Offset、Scroll、Client 属性对比

理解 DOM 元素的三组尺寸和位置属性

问题

offsetscrollclient 这三组属性有什么区别?分别用于什么场景?

解答

三组属性概览

属性组宽高含义位置含义
offset元素完整尺寸(含边框)相对定位父元素的偏移
client可视区域尺寸(不含边框和滚动条)边框宽度
scroll内容完整尺寸(含溢出部分)滚动距离

Offset 系列

const box = document.querySelector('.box');

// offsetWidth = content + padding + h38kz + 滚动条宽度
// offsetHeight = content + padding + h38kz + 滚动条高度
console.log(box.offsetWidth, box.offsetHeight);

// offsetLeft/offsetTop:相对于 offsetParent 的偏移
console.log(box.offsetLeft, box.offsetTop);

// offsetParent:最近的定位祖先(position 非 static)
console.log(box.offsetParent);

Client 系列

const box = document.querySelector('.box');

// clientWidth = content + padding(不含边框和滚动条)
// clientHeight = content + padding(不含边框和滚动条)
console.log(box.clientWidth, box.clientHeight);

// clientLeft/clientTop:左边框和上边框的宽度
console.log(box.clientLeft, box.clientTop);

Scroll 系列

const box = document.querySelector('.box');

// scrollWidth/scrollHeight:内容的完整尺寸(包括溢出不可见部分)
console.log(box.scrollWidth, box.scrollHeight);

// scrollLeft/scrollTop:已滚动的距离(可读写)
console.log(box.scrollLeft, box.scrollTop);

// 滚动到指定位置
box.scrollTop = 100;

可视化示例

<!DOCTYPE html>
<html>
<head>
  <style>
    .container {
      position: relative;
      padding: 20px;
    }
    .box {
      width: 200px;
      height: 150px;
      padding: 20px;
      border: 10px solid #333;
      margin: 30px;
      overflow: auto;
    }
    .content {
      width: 300px;
      height: 400px;
      background: linear-gradient(#f0f0f0, #ccc);
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="box">
      <div class="content">滚动内容</div>
    </div>
  </div>

  <script>
    const box = document.querySelector('.box');
    
    // Offset:元素完整尺寸
    // 200 + 20*2 + 10*2 = 260
    console.log('offsetWidth:', box.offsetWidth);  // 260
    console.log('offsetHeight:', box.offsetHeight); // 210
    console.log('offsetLeft:', box.offsetLeft);     // 30(margin)
    
    // Client:可视区域(不含边框和滚动条)
    // 200 + 20*2 = 240(减去滚动条约17px)
    console.log('clientWidth:', box.clientWidth);   // ~223
    console.log('clientHeight:', box.clientHeight); // 190
    console.log('clientLeft:', box.clientLeft);     // 10(边框宽度)
    
    // Scroll:内容完整尺寸
    console.log('scrollWidth:', box.scrollWidth);   // 300(内容宽度)
    console.log('scrollHeight:', box.scrollHeight); // 440(内容高度+padding)
    
    // 监听滚动
    box.addEventListener('scroll', () => {
      console.log('scrollTop:', box.scrollTop);
    });
  </script>
</body>
</html>

常见应用场景

// 1. 判断元素是否滚动到底部
function isScrolledToBottom(el) {
  return el.scrollHeight - el.scrollTop === el.clientHeight;
}

// 2. 获取元素在页面中的绝对位置
function getAbsolutePosition(el) {
  let left = 0, top = 0;
  while (el) {
    left += el.offsetLeft;
    top += el.offsetTop;
    el = el.offsetParent;
  }
  return { left, top };
}

// 3. 判断是否有滚动条
function hasScrollbar(el) {
  return el.scrollHeight > el.clientHeight;
}

// 4. 获取滚动条宽度
function getScrollbarWidth(el) {
  return el.offsetWidth - el.clientWidth - el.clientLeft * 2;
}

关键点

  • offsetWidth/Height:元素占据的完整空间,包含 content + padding + border
  • clientWidth/Height:元素内部可视区域,包含 content + padding,不含边框和滚动条
  • scrollWidth/Height:元素内容的完整尺寸,包括溢出隐藏的部分
  • scrollTop/Left 是可读写的,其他属性都是只读
  • 判断滚动到底:scrollHeight - scrollTop === clientHeight