Javascript.info-浏览器2

JavaScript 中有许多属性可让我们读取有关元素宽度、高度和其他几何特征的信息。

JavaScript 中有许多属性可让我们读取有关元素宽度、高度和其他几何特征的信息。

我们在 JavaScript 中移动或定位元素时,我们会经常需要它们。

示例元素

作为演示属性的示例元素,我们将使用下面给出的元素:

html
<div id="example">
  ...Text...
</div>
<style>
  #example {
    width: 300px;
    height: 200px;
    border: 25px solid #E8C48F;
    padding: 20px;
    overflow: auto;
  }
</style>

它有边框(border),内边距(padding)和滚动(scrolling)等全套功能。但没有外边距(margin),因为它们不是元素本身的一部分,并且它们没什么特殊的属性。

这个元素看起来就像这样:

image-20251112163531948

备注

注意滚动条

上图演示了元素具有滚动条这种最复杂的情况。一些浏览器(并非全部)通过从内容(上面标记为 “content width”)中获取空间来为滚动条保留空间。

因此,如果没有滚动条,内容宽度将是 300 px,但是如果滚动条宽度是 16px(不同的设备和浏览器,滚动条的宽度可能有所不同),那么还剩下 300 - 16 = 284px,我们应该考虑到这一点。这就是为什么本章的例子总是假设有滚动条。如果没有滚动条,一些计算会更简单。

这就是为啥有的时候出现滚动条会是内容位置出现变化

备注

文本可能会溢出到 padding-bottom

在我们的插图中的 padding 中通常显示为空,但是如果元素中有很多文本,并且溢出了,那么浏览器会在 padding-bottom 处显示“溢出”文本,这是正常现象。

几何

这是带有几何属性的整体图片:

image-20251112163621088

这些属性的值在技术上讲是数字,但这些数字其实是“像素(pixel)”,因此它们是像素测量值。

让我们从元素外部开始探索属性。

offsetParent,offsetLeft/Top

这些属性很少使用,但它们仍然是“最外面”的几何属性,所以我们将从它们开始。

offsetParent 是最接近的祖先(ancestor),在浏览器渲染期间,它被用于计算坐标。

最近的祖先为下列之一:

  1. CSS 定位的(positionabsoluterelativefixedsticky),
  2. <td><th><table>
  3. <body>

属性 offsetLeft/offsetTop 提供相对于 offsetParent 左上角的 x/y 坐标。(是不是设置left/top对应的parent作为offsetParent来计算的)

在下面这个例子中,内部的 <div><main> 作为 offsetParent,并且 offsetLeft/offsetTop 让它从左上角位移(180):

html
<main style="position: relative" id="main">
  <article>
    <div id="example" style="position: absolute; left: 180px; top: 180px">...</div>
  </article>
</main>
<script>
  alert(example.offsetParent.id); // main
  alert(example.offsetLeft); // 180(注意:这是一个数字,不是字符串 "180px")
  alert(example.offsetTop); // 180
</script>

image-20251112163642170

有以下几种情况下,offsetParent 的值为 null

  1. 对于未显示的元素(display:none 或者不在文档中)。
  2. 对于 <body><html>
  3. 对于带有 position:fixed 的元素。

offsetWidth/offsetHeight

现在,让我们继续关注元素本身

这两个属性是最简单的。它们提供了元素的“外部” width/height。或者,换句话说,它的完整大小(包括边框)。

image-20251112163708898

对于我们的示例元素:

  • offsetWidth = 390 —— 外部宽度(width),可以计算为内部 CSS-width(300px)加上 padding(2 * 20px)和 border(2 * 25px)。
  • offsetHeight = 290 —— 外部高度(height)。

备注

对于未显示的元素,几何属性为 0/null

仅针对显示的元素计算几何属性。

如果一个元素(或其任何祖先)具有 display:none 或不在文档中,则所有几何属性均为零(或 offsetParentnull)。

例如,当我们创建了一个元素,但尚未将其插入文档中,或者它(或它的祖先)具有 display:none 时,offsetParentnull,并且 offsetWidthoffsetHeight0

clientTop/clientLeft

在元素内部,我们有边框(border)。

为了测量它们,可以使用 clientTopclientLeft

在我们的例子中:

  • clientLeft = 25 —— 左边框宽度
  • clientTop = 25 —— 上边框宽度

image-20251112163803180

……但准确地说 —— 这些属性不是边框的 width/height,而是内侧与外侧的相对坐标。

有什么区别?

当文档从右到左显示(操作系统为阿拉伯语或希伯来语)时,影响就显现出来了。此时滚动条不在右边,而是在左边,此时 clientLeft 则包含了滚动条的宽度。

在这种情况下,clientLeft 的值将不是 25,而是加上滚动条的宽度 25 + 16 = 41

这是希伯来语的例子:

image-20251112163819489

clientWidth/Height

这些属性提供了元素边框内区域的大小。

它们包括了 “content width” 和 “padding”,但不包括滚动条宽度(scrollbar):

image-20251112163837170

在上图中,我们首先考虑 clientHeight

这里没有水平滚动条,所以它恰好是 border 内的总和:CSS-height 200px 加上顶部和底部的 padding(2 * 20px),总计 240px

现在 clientWidth —— 这里的 “content width” 不是 300px,而是 284px,因为被滚动条占用了 16px。所以加起来就是 284px 加上左侧和右侧的 padding,总计 324px

如果这里没有 padding,那么 clientWidth/Height 代表的就是内容区域,即 border 和 scrollbar(如果有)内的区域。

image-20251112163853934

因此,当没有 padding 时,我们可以使用 clientWidth/clientHeight 来获取内容区域的大小。

scrollWidth/scrollHeight

这些属性就像 clientWidth/clientHeight,但它们还包括滚动出(隐藏)的部分

image-20251112163915948

在上图中:

  • scrollHeight = 723 —— 是内容区域的完整内部高度,包括滚动出的部分。
  • scrollWidth = 324 —— 是完整的内部宽度,这里我们没有水平滚动,因此它等于 clientWidth

我们可以使用这些属性将元素展开(expand)到整个 width/height。

像这样:

javascript
// 将元素展开(expand)到完整的内容高度
element.style.height = `${element.scrollHeight}px`;

image-20251112164001500

scrollLeft/scrollTop

属性 scrollLeft/scrollTop 是元素的隐藏、滚动部分的 width/height

在下图中,我们可以看到带有垂直滚动块的 scrollHeightscrollTop

image-20251112164026141

换句话说,scrollTop 就是“已经滚动了多少”。

备注

scrollLeft/scrollTop 是可修改的

大多数几何属性是只读的,但是 scrollLeft/scrollTop 是可修改的,并且浏览器会滚动该元素。

如果你点击下面的元素,则会执行代码 elem.scrollTop += 10。这使得元素内容向下滚动 10px

scrollTop 设置为 0 或一个大的值,例如 1e9,将会使元素滚动到顶部/底部。

不要从 CSS 中获取 width/height

我们刚刚介绍了 DOM 元素的几何属性,它们可用于获得宽度、高度和计算距离。

但是,正如我们在 样式和类 一章所知道的那样,我们可以使用 getComputedStyle 来读取 CSS-width 和 height。

那为什么不像这样用 getComputedStyle 读取元素的 width 呢?

javascript
let elem = document.body;
alert( getComputedStyle(elem).width ); // 显示 elem 的 CSS width

为什么我们应该使用几何属性呢?这里有两个原因:

  1. 首先,CSS width/height 取决于另一个属性:box-sizing,它定义了“什么是” CSS 宽度和高度。出于 CSS 的目的而对 box-sizing 进行的更改可能会破坏此类 JavaScript 操作。

  2. 其次,CSS 的 width/height 可能是 auto,例如内联(inline)元素:

    <span id="elem">Hello!</span>
    
    <script>
      alert( getComputedStyle(elem).width ); // auto
    </script>

    从 CSS 的观点来看,width:auto 是完全正常的,但在 JavaScript 中,我们需要一个确切的 px 大小,以便我们在计算中使用它。因此,这里的 CSS 宽度没什么用。

还有另一个原因:滚动条。有时,在没有滚动条的情况下代码工作正常,当出现滚动条时,代码就出现了 bug,因为在某些浏览器中,滚动条会占用内容的空间。因此,可用于内容的实际宽度小于 CSS 宽度。而 clientWidth/clientHeight 则会考虑到这一点。

……但是,使用 getComputedStyle(elem).width 时,情况就不同了。某些浏览器(例如 Chrome)返回的是实际内部宽度减去滚动条宽度,而某些浏览器(例如 Firefox)返回的是 CSS 宽度(忽略了滚动条)。这种跨浏览器的差异是不使用 getComputedStyle 而依靠几何属性的原因。

html
<div style="width: 300px;height: 300px;overflow-y: scroll;background-color: pink;">
  <button onclick="alert( getComputedStyle(elem).width )">获取计算宽度</button>
</div>
<script>
  const elem = document.querySelector('div')
</script>

image-20251112164553097

在桌面 Windows 操作系统上,Firefox、Chrome、Edge 都为滚动条保留了空间。但 Firefox 显示的是 300px,而 Chrome 和 Edge 显示较少。这是因为 Firefox 返回 CSS 宽度,其他浏览器返回“真实”宽度。

请注意,所描述的差异只是关于从 JavaScript 读取的 getComputedStyle(...).width,而视觉上看,一切都是正确的。

总结

元素具有以下几何属性:

  • offsetParent —— 是最接近的 CSS 定位的祖先,或者是 tdthtablebody
  • offsetLeft/offsetTop —— 是相对于 offsetParent 的左上角边缘的坐标。
  • offsetWidth/offsetHeight —— 元素的“外部” width/height,边框(border)尺寸计算在内。
  • clientLeft/clientTop —— 从元素左上角外角到左上角内角的距离。对于从左到右显示内容的操作系统来说,它们始终是左侧/顶部 border 的宽度。而对于从右到左显示内容的操作系统来说,垂直滚动条在左边,所以 clientLeft 也包括滚动条的宽度。
  • clientWidth/clientHeight —— 内容的 width/height,包括 padding,但不包括滚动条(scrollbar)。
  • scrollWidth/scrollHeight —— 内容的 width/height,就像 clientWidth/clientHeight 一样,但还包括元素的滚动出的不可见的部分。
  • scrollLeft/scrollTop —— 从元素的左上角开始,滚动出元素的上半部分的 width/height。

除了 scrollLeft/scrollTop 外,所有属性都是只读的。如果我们修改 scrollLeft/scrollTop,浏览器会滚动对应的元素。

Window 大小和滚动

我们如何找到浏览器窗口(window)的宽度和高度呢?我们如何获得文档(document)的包括滚动部分在内的完整宽度和高度呢?我们如何使用 JavaScript 滚动页面?

对于此类信息,我们可以使用与 <html> 标签相对应的根文档元素 document.documentElement。但是还有其他方法和特性需要考虑。

窗口的 width/height

为了获取窗口(window)的宽度和高度,我们可以使用 document.documentElementclientWidth/clientHeight

image-20251112164710658

警告

不是 window.innerWidth/innerHeight

浏览器也支持像 window.innerWidth/innerHeight 这样的属性。它们看起来像我们想要的,那为什么不使用它们呢?

如果这里存在一个滚动条,并且滚动条占用了一些空间,那么 clientWidth/clientHeight 会提供没有滚动条(减去它)的 width/height。换句话说,它们返回的是可用于内容的文档的可见部分的 width/height。

window.innerWidth/innerHeight 包括了滚动条。

如果这里有一个滚动条,它占用了一些空间,那么这两行代码会显示不同的值:

javascript
alert( window.innerWidth ); // 整个窗口的宽度
alert( document.documentElement.clientWidth ); // 减去滚动条宽度后的窗口宽度

在大多数情况下,我们需要 可用 的窗口宽度以绘制或放置某些东西。也就是说,在滚动条内(如果有)。所以,我们应该使用 documentElement.clientHeight/clientWidth

警告

DOCTYPE 很重要

请注意:当 HTML 中没有 <!DOCTYPE HTML> 时,顶层级(top-level)几何属性的工作方式可能就会有所不同。可能会出现一些稀奇古怪的情况。

在现代 HTML 中,我们始终都应该写 DOCTYPE

文档的 width/height

从理论上讲,由于根文档元素是 document.documentElement,并且它包围了所有内容,因此我们可以通过使用 documentElement.scrollWidth/scrollHeight 来测量文档的完整大小。

但是在该元素上,对于整个文档,这些属性均无法正常工作。在 Chrome/Safari/Opera 中,如果没有滚动条,documentElement.scrollHeight 甚至可能小于 documentElement.clientHeight!很奇怪,对吧?

为了可靠地获得完整的文档高度,我们应该采用以下这些属性的最大值:

javascript
let scrollHeight = Math.max(
  document.body.scrollHeight, document.documentElement.scrollHeight,
  document.body.offsetHeight, document.documentElement.offsetHeight,
  document.body.clientHeight, document.documentElement.clientHeight
);

alert('Full document height, with scrolled out part: ' + scrollHeight);

为什么这样?最好不要问。这些不一致来源于远古时代,而不是“聪明”的逻辑。

获得当前滚动

DOM 元素的当前滚动状态在其 scrollLeft/scrollTop 属性中。

对于文档滚动,在大多数浏览器中,我们可以使用 document.documentElement.scrollLeft/scrollTop,但在较旧的基于 WebKit 的浏览器中则不行,例如在 Safari(bug 5991)中,我们应该使用 document.body 而不是 document.documentElement

幸运的是,我们根本不必记住这些特性,因为我们可以从 window.pageXOffset/pageYOffset 中获取页面当前滚动信息:

javascript
alert('当前已从顶部滚动:' + window.pageYOffset);
alert('当前已从左侧滚动:' + window.pageXOffset);

这些属性是只读的。

备注

我们也可以从 windowscrollXscrollY 属性中获取滚动信息

由于历史原因,存在了这两种属性,但它们是一样的:

  • window.pageXOffsetwindow.scrollX 的别名。
  • window.pageYOffsetwindow.scrollY 的别名。

滚动:scrollTo,scrollBy,scrollIntoView

重要

必须在 DOM 完全构建好之后才能通过 JavaScript 滚动页面。

例如,如果我们尝试通过 <head> 中的脚本滚动页面,它将无法正常工作。

1. window.scrollTo 的两种写法

① 旧写法:两个数字参数

// 滚动到文档左上角(0,0)
window.scrollTo(0, 0);

// 滚动到 x=0, y=500 的位置
window.scrollTo(0, 500);
  • 第一个参数:x(横向滚动距离,单位 px)
  • 第二个参数:y(纵向滚动距离,单位 px)

这是最早的形式,兼容性最好,但不能设置平滑滚动


② 新写法:配置对象(推荐)

window.scrollTo({
  top: 500,         // 必需:纵向滚动距离
  left: 0,          // 可选:横向滚动距离,默认 0
  behavior: "smooth" // "auto" | "smooth"
});

参数说明:

  • top:目标纵向滚动位置(类似旧写法里的 y)
  • left:目标横向滚动位置(类似旧写法里的 x)
  • behavior
    • "auto":直接跳过去(默认)
    • "smooth":平滑滚动 ✅

2. window.scrollBy 的两种写法

scrollBy 是“相对当前滚动位置”进行滚动。

① 旧写法:两个数字参数

// 向下滚动 300px
window.scrollBy(0, 300);

// 向右滚动 100px,向下滚动 200px
window.scrollBy(100, 200);
  • 第一个参数:相对当前的 x 增量
  • 第二个参数:相对当前的 y 增量

② 新写法:配置对象(推荐)

window.scrollBy({
  top: 300,          // 相对当前向下 300px
  left: 0,           // 相对当前向右 0px
  behavior: "smooth" // 平滑滚动
});

scrollTo 的对象形式几乎一样,只是语义是“相对滚动”而不是“滚到绝对位置”。


3. window.scroll 是什么?

还有一个容易混淆的:

window.scroll(0, 500);
window.scroll({ top: 500, behavior: "smooth" });
  • window.scroll 基本上就是 window.scrollTo 的别名
  • 也支持 (x, y)options 对象 两种写法

可以通过更改 scrollTop/scrollLeft 来滚动常规元素。

我们可以使用 document.documentElement.scrollTop/scrollLeft 对页面进行相同的操作(Safari 除外,而应该使用 document.body.scrollTop/Left 代替)。

或者,有一个更简单的通用解决方案:使用特殊方法 window.scrollBy(x,y)window.scrollTo(pageX,pageY)

  • 方法 scrollBy(x,y) 将页面滚动至 相对于当前位置的 (x, y) 位置。例如,scrollBy(0,10) 会将页面向下滚动 10px

    下面这个按钮演示了这个方法:

    js
    window.scrollBy(0,10)
  • 方法 scrollTo(pageX,pageY) 将页面滚动至 绝对坐标,使得可见部分的左上角具有相对于文档左上角的坐标 (pageX, pageY)。就像设置了 scrollLeft/scrollTop 一样。

    要滚动到最开始,我们可以使用 scrollTo(0,0)

    js
    window.scrollTo(0,0)
    behave参数?
    思华滚动?
    对向形式?
    el.scrollTo({top:0,left:0,behave:'smooth'})

这些方法适用于所有浏览器。

scrollIntoView

为了完整起见,让我们再介绍一种方法:elem.scrollIntoView(top)。

elem.scrollIntoView 是 DOM 元素的方法,用来让当前元素滚动到浏览器可视区域中。


1. 基本语法

有三种常见写法:

// 1. 不带参数
element.scrollIntoView();

// 2. 布尔参数(老写法)
element.scrollIntoView(alignToTop);

// 3. 配置对象(推荐)
element.scrollIntoView(options);

其中 element 就是一个 DOM 元素对象,比如通过 document.querySelector() 拿到的节点。


2. 布尔参数写法(旧)

element.scrollIntoView(true);  // 元素滚到可视区域顶部(默认)
element.scrollIntoView(false); // 元素滚到可视区域底部
  • true:元素的顶端对齐到滚动容器的可视区域顶端
  • false:元素的底部对齐到可视区域底部

这种写法兼容性很好,但不够灵活。


3. 配置对象写法(推荐)

element.scrollIntoView({
  behavior: "smooth",   // "auto" | "smooth"
  block: "start",       // "start" | "center" | "end" | "nearest"
  inline: "nearest"     // "start" | "center" | "end" | "nearest"
});

参数说明

  • behavior
    • "auto":立刻滚动(默认)
    • "smooth":平滑滚动(常用)
  • block(垂直方向对齐方式)
    • "start":让元素的顶部对齐到可视区域的顶部
    • "center":让元素在可视区域垂直居中
    • "end":让元素的底部对齐到可视区域底部
    • "nearest":滚动最少的距离使其可见(默认)
  • inline(水平方向对齐方式,含义与 block 类似)
    • "start" | "center" | "end" | "nearest"

elem.scrollIntoView(top) 的调用将滚动页面以使 elem 可见。它有一个参数:

  • 如果 top=true(默认值),页面滚动,使 elem 出现在窗口顶部。元素的上边缘将与窗口顶部对齐。
  • 如果 top=false,页面滚动,使 elem 出现在窗口底部。元素的底部边缘将与窗口底部对齐。

下面这个按钮会滚动页面,以使其自身定位在窗口顶部:

js
this.scrollIntoView()

下面这个按钮会滚动页面,以使其自身定位在窗口底部:

js
this.scrollIntoView(false)

禁止滚动

有时候我们需要使文档“不可滚动”。例如,当我们需要用一条需要立即引起注意的大消息来覆盖文档时,我们希望访问者与该消息而不是与文档进行交互。

要使文档不可滚动,只需要设置 document.body.style.overflow = "hidden"。该页面将“冻结”在其当前滚动位置上。

试一试:

js
document.body.style.overflow = ‘hidden’
js
document.body.style.overflow = ‘’

第一个按钮用于冻结滚动,第二个按钮则用于恢复滚动。

我们还可以使用相同的技术来冻结其他元素的滚动,而不仅仅是 document.body

这个方法的缺点是会使滚动条消失。如果滚动条占用了一些空间,它原本占用的空间就会空出来,那么内容就会“跳”进去以填充它。

这看起来有点奇怪,但是我们可以对比冻结前后的 clientWidth。如果它增加了(滚动条消失后),那么我们可以在 document.body 中滚动条原来的位置处通过添加 padding,来替代滚动条,这样这个问题就解决了。保持了滚动条冻结前后文档内容宽度相同。

总结

几何:

  • 文档可见部分的 width/height(内容区域的 width/height):document.documentElement.clientWidth/clientHeight

  • 整个文档的 width/height,其中包括滚动出去的部分:

    javascript
    let scrollHeight = Math.max(
      document.body.scrollHeight, document.documentElement.scrollHeight,
      document.body.offsetHeight, document.documentElement.offsetHeight,
      document.body.clientHeight, document.documentElement.clientHeight
    );

滚动:

读取当前的滚动:window.pageYOffset/pageXOffset

更改当前的滚动:

  • window.scrollTo(pageX,pageY) —— 绝对坐标,
  • window.scrollBy(x,y) —— 相对当前位置进行滚动,
  • elem.scrollIntoView(top) —— 滚动以使 elem 可见(elem 与窗口的顶部/底部对齐)。

坐标

要移动页面的元素,我们应该先熟悉坐标。

大多数 JavaScript 方法处理的是以下两种坐标系中的一个:

  1. 相对于窗口 —— 类似于 position:fixed,从窗口的顶部/左侧边缘计算得出。
    • 我们将这些坐标表示为 clientX/clientY,当我们研究事件属性时,就会明白为什么使用这种名称来表示坐标。
  2. 相对于文档 —— 与文档根(document root)中的 position:absolute 类似,从文档的顶部/左侧边缘计算得出。
    • 我们将它们表示为 pageX/pageY

当页面滚动到最开始时,此时窗口的左上角恰好是文档的左上角,它们的坐标彼此相等。但是,在文档移动之后,元素的窗口相对坐标会发生变化,因为元素在窗口中移动,而元素在文档中的相对坐标保持不变。

在下图中,我们在文档中取一点,并演示了它滚动之前(左)和之后(右)的坐标:

image-20251112165045754

当文档滚动了:

  • pageY —— 元素在文档中的相对坐标保持不变,从文档顶部(现在已滚动出去)开始计算。
  • clientY —— 窗口相对坐标确实发生了变化(箭头变短了),因为同一个点越来越靠近窗口顶部。

元素坐标:getBoundingClientRect

方法 elem.getBoundingClientRect() 返回最小矩形的窗口坐标,该矩形将 elem 作为内建 DOMRect 类的对象。

主要的 DOMRect 属性:

  • x/y —— 矩形原点相对于窗口的 X/Y 坐标,
  • width/height —— 矩形的 width/height(可以为负)。

此外,还有派生(derived)属性:

  • top/bottom —— 顶部/底部矩形边缘的 Y 坐标,
  • left/right —— 左/右矩形边缘的 X 坐标。

例如,点击下面这个按钮以查看其窗口坐标:

button.getBoundingClientRect()

如果你滚动此页面并重复点击上面那个按钮,你会发现随着窗口相对按钮位置的改变,其窗口坐标(如果你垂直滚动页面,则为 y/top/bottom)也随之改变。

下面这张是 elem.getBoundingClientRect() 的输出的示意图:

image-20251112165151858

正如你所看到的,x/ywidth/height 对矩形进行了完整的描述。可以很容易地从它们计算出派生(derived)属性:

  • left = x
  • top = y
  • right = x + width
  • bottom = y + height

请注意:

  • 坐标可能是小数,例如 10.5。这是正常的,浏览器内部使用小数进行计算。在设置 style.left/top 时,我们不是必须对它们进行舍入。
  • 坐标可能是负数。例如滚动页面,使 elem 现在位于窗口的上方,则 elem.getBoundingClientRect().top 为负数。

备注

为什么需要派生(derived)属性?如果有了 x/y,为什么还要还会存在 top/left

从数学上讲,一个矩形是使用其起点 (x,y) 和方向向量 (width,height) 唯一定义的。因此,其它派生属性是为了方便起见。

从技术上讲,width/height 可能为负数,从而允许“定向(directed)”矩形,例如代表带有正确标记的开始和结束的鼠标选择。

负的 width/height 值表示矩形从其右下角开始,然后向左上方“增长”。

这是一个矩形,其 widthheight 均为负数(例如 width=-200height=-100):

image-20251112165226140

正如你所看到的,在这个例子中,left/topx/y 不相等。

但是实际上,elem.getBoundingClientRect() 总是返回正数的 width/height,这里我们提及负的 width/height 只是为了帮助你理解,为什么这些看起来重复的属性,实际上并不是重复的。

IE 浏览器不支持 x/y

由于历史原因,IE 浏览器不支持 x/y 属性。

因此,我们可以写一个 polyfill(在 DomRect.prototype 中添加一个 getter),或者仅使用 top/left,因为对于正值的 width/height 来说,它们和 x/y 一直是一样的,尤其是对于 elem.getBoundingClientRect() 的结果。

坐标的 right/bottom 与 CSS position 属性不同

相对于窗口(window)的坐标和 CSS position:fixed 之间有明显的相似之处。

但是在 CSS 定位中,right 属性表示距右边缘的距离,而 bottom 属性表示距下边缘的距离。

elementFromPoint(x, y)

document.elementFromPoint(x, y) 的调用会返回在窗口坐标 (x, y)嵌套最多(the most nested)的元素。

语法如下:

javascript
let elem = document.elementFromPoint(x, y);

例如,下面的代码会高亮显示并输出现在位于窗口中间的元素的标签:

javascript
let centerX = document.documentElement.clientWidth / 2;
let centerY = document.documentElement.clientHeight / 2;

let elem = document.elementFromPoint(centerX, centerY);

elem.style.background = "red";
alert(elem.tagName);

因为它使用的是窗口坐标,所以元素可能会因当前滚动位置而有所不同

备注

对于在窗口之外的坐标,elementFromPoint 返回 null

方法 document.elementFromPoint(x,y) 只对在可见区域内的坐标 (x,y) 起作用。

如果任何坐标为负或者超过了窗口的 width/height,那么该方法就会返回 null

在大多数情况下,这种行为并不是一个问题,但是我们应该记住这一点。

如果我们没有对其进行检查,可能就会发生下面这个典型的错误:

javascript
let elem = document.elementFromPoint(x, y);
// 如果坐标恰好在窗口外,则 elem = null
elem.style.background = ''; // Error!

用于 “fixed” 定位

大多数时候,我们需要使用坐标来确定某些内容的位置。

想要在某元素附近展示内容,我们可以使用 getBoundingClientRect 来获取这个元素的坐标,然后使用 CSS position 以及 left/top(或 right/bottom)。

例如,下面的函数 createMessageUnder(elem, html)elem 下显示了消息:

javascript
let elem = document.getElementById("coords-show-mark");

function createMessageUnder(elem, html) {
  // 创建 message 元素
  let message = document.createElement('div');
  // 在这里最好使用 CSS class 来定义样式
  message.style.cssText = "position:fixed; color: red";
  // 分配坐标,不要忘记 "px"!
  let coords = elem.getBoundingClientRect();
  message.style.left = coords.left + "px";
  message.style.top = coords.bottom + "px";

  message.innerHTML = html;

  return message;
}

// 用法:
// 在文档中添加 message 保持 5 秒
let message = createMessageUnder(elem, 'Hello, world!');
document.body.append(message);
setTimeout(() => message.remove(), 5000);

点击下面这个按钮来运行它:

Button with id=“coords-show-mark”, the message will appear under it

但是请注意一个重要的细节:滚动页面时,消息就会从按钮流出。

原因很显然:message 元素依赖于 position:fixed,因此当页面滚动时,它仍位于窗口的同一位置。

要改变这一点,我们需要使用基于文档(document)的坐标和 position:absolute 样式。

文档坐标

文档相对坐标从文档的左上角开始计算,而不是窗口。

在 CSS 中,窗口坐标对应于 position:fixed,而文档坐标与顶部的 position:absolute 类似。

我们可以使用 position:absolutetop/left 来把某些内容放到文档中的某个位置,以便在页面滚动时,元素仍能保留在该位置。但是我们首先需要正确的坐标。

这里没有标准方法来获取元素的文档坐标。但是写起来很容易。

这两个坐标系统通过以下公式相连接:

  • pageY = clientY + 文档的垂直滚动出的部分的高度。
  • pageX = clientX + 文档的水平滚动出的部分的宽度。

函数 getCoords(elem) 将从 elem.getBoundingClientRect() 获取窗口坐标,并向其中添加当前滚动:

javascript
// 获取元素的文档坐标
function getCoords(elem) {
  let box = elem.getBoundingClientRect();
  return {
    top: box.top + window.pageYOffset,
    right: box.right + window.pageXOffset,
    bottom: box.bottom + window.pageYOffset,
    left: box.left + window.pageXOffset
  };
}

如果在上面的示例中,我们将其与 position:absolute 一起使用,则在页面滚动时,消息仍停留在元素附近。

修改后的 createMessageUnder 函数:

javascript
function createMessageUnder(elem, html) {
  let message = document.createElement('div');
  message.style.cssText = "position:absolute; color: red";
  let coords = getCoords(elem);
  message.style.left = coords.left + "px";
  message.style.top = coords.bottom + "px";
  message.innerHTML = html;
  return message;
}

评论

评论加载中……