深入解析JavaScript元素位置计算API:Scroll、Client、Offset与getBoundingClientRect

作者:白帽猫时间:2026-10-24 15:40:14浏览:332

本文系统梳理JavaScript中用于元素位置与尺寸计算的四大类API:scroll、client、offset及getBoundingClientRect。详细解析各属性的定义、计算规则及RTL模式下的特殊行为,结合源码示例演示滚动条检测、视口可见性判断及绝对偏移量计算的实际应用,并提醒开发者注意不合理使用可能引发的Layout Thrashing性能问题。

引言

文章中涉及到的api列表:

  1. scroll相关Api
  2. client相关Api
  3. offset相关Api
  4. Element.getBoundingClientRectAPi
  5. Window.getComputedStyleApi

我们将结合API定义及知名开源库的应用场景,逐层分析这些API。掌握这些知识足以应对工作中关于元素位置计算的大部分场景。

需注意,在使用位置计算API时要格外谨慎,不合理的使用可能会造成布局抖动(Layout Thrashing),进而影响页面渲染性能。

scroll

首先我们来查看scroll相关的属性和方法。

Element.scroll()

Element.scroll()是Element接口的方法,用于将指定元素滚动到特定的坐标位置。


element.scroll(x-coord, y-coord)
element.scroll(options)
  • x-coord表示希望在元素左上方区域横轴方向上显示的像素值。
  • y-coord表示希望在元素左上方区域纵轴方向上显示的像素值。

即调用element.scroll(x, y)会将元素的滚动条位置滚动到对应的x和y坐标处。

该方法同时也支持element.scroll(options)形式的调用,允许传入额外的配置对象。


{
    left: number, 
    top: number,
    behavior: 'smooth' | 'auto' // 平滑滚动还是默认直接滚动
}

Element.scrollHeight/scrollWidth

Element.scrollHeight是一个只读属性,用于度量元素内容的高度,包括因溢出导致在视图中不可见的内容。

scrollHeight的值等于该元素在不使用滚动条的情况下,为了适应视口中所用内容所需的最小高度。在没有垂直滚动条的情况下,scrollHeight的值与元素视图填充所有内容所需的最小值clientHeight相同。该属性包括元素的padding,但不包括border和margin。此外,scrollHeight还包含::before和::after等伪元素的高度。

换句话说,当元素不存在滚动条时,Element.scrollHeight恒等于clientHeight。但如果出现了滚动条,scrollHeight指的是包含元素不可见内容的高度,此时scrollHeight恒大于clientHeight。

  • Element.scrollWidth同样是一个元素内容宽度的只读属性,包含由于溢出导致视图中不可见的内容。

其原理与scrollHeight同理,只是这里衡量的是宽度而非高度。

简而言之,如果一个元素不存在滚动条,那么其scroll和client属性的值是相等的。如果存在滚动条,client仅计算当前元素展示出来的高度或宽度,而scroll不仅计算当前展示部分,还包含被滚动条隐藏的内容的高度或宽度。

公式可表示为:clientWidth/height + [滚动条被隐藏内容宽度/高度] = scrollWidth/Height。

Element.scrollLeft/scrollTop

  • Element.scrollTop属性用于获取或设置一个元素的内容垂直滚动的像素数。
  • Element.scrollLeft属性用于读取或设置元素滚动条到元素左边的距离。

需要特别注意:如果元素的内容排列方向(direction)是rtl(right-to-left,从右向左),那么滚动条会位于最右侧(内容开始处),此时scrollLeft值为0。当从右向左拖动滚动条时,scrollLeft的值会从0变为负数。

scrollLeft和scrollTop是日常工作中频繁使用的操作滚动条的API,它们是可设置的值。通过设置不同的值,可以控制滚动条的位置。

实际上,这两个属性与上方的Element.scroll()方法可以达到相同的效果。

在实际工作中,如果对滚动操作有频繁需求,建议使用better-scroll。它是一个适用于移动/Web端的通用JS滚动库,内部基于元素transform操作滚动,不会触发相关的重排或回流。

判断当前元素是否存在滚动条

出现滚动条意味着元素空间将大于其内容显示区域,根据这一现象可以得到判断是否出现滚动条的规则。


export const hasScrolled = (element, direction) => {
  if (!element || element.nodeType !== 1) return;
  if (direction === "vertical") {
    return element.scrollHeight > element.clientHeight;
  } else if (direction === "horizontal") {
    return element.scrollWidth > element.clientWidth;
  }
};

判断用户是否滚动到底部

本质上,当元素出现滚动条时,判断当前元素出现的高度加上滚动条高度等于元素本身的高度(包含隐藏部分)。


element.scrollHeight - element.scrollTop === element.clientHeight

client

MouseEvent.clientX/Y

MouseEvent.clientX/Y同样是只读属性,它提供事件发生时的应用客户端区域的水平坐标。

例如,无论页面是否有垂直或水平滚动,当你点击客户端区域的左上角时,鼠标事件的clientX/Y值都将为0。

实际上,MouseEvent.clientX/Y是相对于当前视口(浏览器可视区)进行位置计算的。

转载一张非常直白的图:

引言

Element.clientHeight/clientWidth

Element.clientWidth/clientHeight属性表示元素的内部宽度(或高度),以像素计。该属性包括内边距padding,但不包括边框border、外边距margin和垂直滚动条(如果有的话)。

内联元素以及没有CSS样式的元素的clientWidth属性值为0。

在不出现滚动条时,Element.clientWidth/Height等于Element.scrollWidth/Height。

引言

Element.clientTop/clientLeft

Element.clientLeft表示一个元素的左边框的宽度,以像素表示。如果元素的文本方向是从右向左(RTL, right-to-left),并且由于内容溢出导致左边出现了一个垂直滚动条,则该属性包括滚动条的宽度。clientLeft不包括左外边距和左内边距,且为只读属性。

同样的,Element.clientTop表示元素上边框的宽度,也是一个只读属性。

这两个属性在日常使用较少,但也应了解以避免混淆这些名称相似的属性。

offset

MouseEvent.offsetX/offsetY

MouseEvent接口的只读属性offsetX/Y规定了事件对象与目标节点的内填充边(padding edge)在X/Y轴方向上的偏移量。

相信使用过offset的同学对这个属性深有体会,它是相对于父元素的左边或上方的偏移量。

注意触发元素即e.target,需额外小心:如果事件对象中存在从一个子元素移动到子元素内部时,e.offsetX/Y此时是相对于子元素的左上角偏移量。

offsetWidth/offsetHeight

HTMLElement.offsetWidth/Height是一个只读属性,返回一个元素的布局宽度/高度。

所谓的布局宽度,也就是相对于我们上边说到的clientHeight/Width,offsetHeight/Width都不包含border以及滚动条的宽/高(如果存在的话)。

而offsetWidth/offsetHeight返回元素的布局宽度/高度,包含元素的边框(border)、水平线/垂直线上的内边距(padding)、竖直/水平方向滚动条(scrollbar)(如果存在的话),以及CSS设置的宽度(width)的值。

offsetTop/left

HTMLElement.offsetLeft是一个只读属性,返回当前元素左上角相对于HTMLElement.offsetParent节点的左边界偏移的像素值。

注意返回的是相对于HTMLElement.offsetParent节点左边边界的偏移量。

何为HTMLElement.offsetParent?

HTMLElement.offsetParent是一个只读属性,返回一个指向最近的(指包含层级上的最近)包含该元素的定位元素或者最近的table, td, th, body元素。当元素的style.display设置为"none"时,offsetParent返回null。offsetParent很有用,因为offsetTop和offsetLeft都是相对于其内边距边界的。-- MDN

用通俗的话讲,当前元素的祖先组件节点如果不存在任何table, td, th以及position属性为relative, absolute等为定位元素时,offsetLeft/offsetTop返回的是距离body左/上角的偏移量。

当祖先元素中有定位元素(或上述标签元素)时,它就可以被称为元素的offsetParent。元素的offsetLeft/offsetTop的值等于它的左边框左侧/顶边框顶部到它的offsetParent元素左边框的距离。

我们来看看这张图:

引言

计算元素距离body的偏移量

当我们需要获得元素距离body的距离时,但无法确定父元素是否存在定位元素时(大多数时候在组件开发中,并不清楚父节点是否存在定位)。此时需要实现类似jQuery的offset()方法:获得当前元素对于body的偏移量。

  • 无法直接使用offsetLeft/offsetTop获取,因为并不确定父元素是否存在定位元素。
  • 使用递归解决,累加偏移量offset,当前offsetParent不为body时。
  • 继续递归向上朝着offsetParent累加offset,直到遇到body元素停止。

const getOffsetSize = function(Node: any, offset?: any): any {
  if (!offset) {
    offset = {
      x: 0,
      y: 0
    };
  }
  if (Node === document.body) return offset;
  offset.x = offset.x + Node.offsetLeft;
  offset.y = offset.y + Node.offsetTop;
  return getOffsetSize(Node.offsetParent, offset);
};

注意:这里不可以使用parentNode,上文已经讲过offsetLeft/top针对的是HTMLElement.offsetParent的偏移量而非parentNode的偏移量。

Element.getBoundingClientRect

用法讲解

Element.getBoundingClientRect()方法返回元素的大小及其相对于视口的位置。

element.getBoundingClientRect()返回的是相对于视口左上角的位置。

element.getBoundingClientRect()返回的height和width是针对元素可见区域的宽和高(具体尺寸根据box-sizing决定),并不包含滚动条被隐藏的内容。

TIP:如果是标准盒子模型,元素的尺寸等于width/height + padding + border-width的总和。如果box-sizing: border-box,元素的尺寸等于width/height。


rectObject = object.getBoundingClientRect();

返回值是一个DOMRect对象,这个对象是由该元素的getClientRects()方法返回的一组矩形的集合,即该元素的CSS边框大小。返回的结果是包含完整元素的最小矩形,并且拥有left, top, right, bottom, x, y, width, 和height这几个以像素为单位的只读属性用于描述整个边框。除了width和height以外的属性是相对于视图窗口的左上角来计算的。

width和height是计算元素的大小,其他属性都是相对于视口左上角来说的。

当计算边界矩形时,会考虑视口区域(或其他可滚动元素)内的滚动操作,也就是说,当滚动位置发生了改变,top和left属性值就会随之立即发生变化(因此,它们的值是相对于视口的,而不是绝对的)。如果你需要获得相对于整个网页左上角定位的属性值,那么只要给top、left属性值加上当前的滚动位置(通过window.scrollX和window.scrollY),这样就可以获取与当前的滚动位置无关的值。

引言

计算元素是否出现在视口内

利用的还是元素距离视口的位置小于视口的大小。

注意即便变成了负值,那么也表示元素曾经出现过在屏幕中只是现在不显示了而已(就比如滑动过)。

vue-lazy图片懒加载库源码就是这么判断的。


 isInView (): boolean {
    const rect = this.el.getBoundingClientRect()
    return rect.top < window.innerHeight && rect.left < window.innerWidth
  }

如果rect.top < window.innerHeight表示当前元素已经(过)出现在页面中,left同理。

window.getComputedStyle

用法讲解

Window.getComputedStyle()方法返回一个对象,该对象在应用活动样式表并解析这些值可能包含的任何基本计算后报告元素的所有CSS属性的值。私有的CSS属性值可以通过对象提供的API或通过简单地使用CSS属性名称进行索引来访问。


let style = window.getComputedStyle(element, [pseudoElt]);

element

  • 用于获取计算样式的Element。

pseudoElt可选

  • 指定一个要匹配的伪元素的字符串。必须对普通元素省略(或null)。

返回的style是一个实时的CSSStyleDeclaration对象,当元素的样式更改时,它会自动更新本身。

总结

到此这篇关于js中位置计算的文章就介绍到这了,更多相关js中的位置计算内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!

作者最新文章
相关文章
更多