2023 年 1 月 25 日

坐标

为了移动元素,我们应该熟悉坐标。

大多数 JavaScript 方法处理两个坐标系之一

  1. 相对于窗口 – 类似于 position:fixed,从窗口顶部/左侧边缘计算。
    • 我们将这些坐标表示为 clientX/clientY,这种名称的原因将在我们学习事件属性时变得清晰。
  2. 相对于文档 – 类似于文档根元素中的 position:absolute,从文档顶部/左侧边缘计算。
    • 我们将其表示为 pageX/pageY

当页面滚动到最开始,使得窗口的顶部/左侧角恰好是文档的顶部/左侧角时,这些坐标彼此相等。但在文档移动后,元素的窗口相对坐标会发生变化,因为元素在窗口中移动,而文档相对坐标保持不变。

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

当文档滚动时

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

元素坐标:getBoundingClientRect

方法 elem.getBoundingClientRect() 返回内置 DOMRect 类的对象,该对象表示包围 elem 的最小矩形的窗口坐标。

主要 DOMRect 属性

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

此外,还有派生属性

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

例如,单击此按钮以查看其窗口坐标

如果您滚动页面并重复,您会注意到,随着窗口相对按钮位置的变化,其窗口坐标(如果您垂直滚动,则为 y/top/bottom)也会发生变化。

以下是 elem.getBoundingClientRect() 输出的图片

如您所见,x/ywidth/height 完全描述了矩形。派生属性可以轻松地从它们计算出来

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

请注意

  • 坐标可能是小数,例如 10.5。这是正常的,浏览器在内部计算中使用小数。当设置到 style.left/top 时,我们不必对它们进行四舍五入。
  • 坐标可能是负数。例如,如果页面滚动到 elem 现在位于窗口上方,则 elem.getBoundingClientRect().top 为负数。
为什么需要派生属性?如果存在 x/y,为什么 top/left 存在?

在数学上,一个矩形由其起始点 (x,y) 和方向向量 (width,height) 唯一定义。因此,其他派生属性是为了方便。

从技术上讲,width/height 可能为负,这允许使用“定向”矩形,例如,用正确标记的开始和结束来表示鼠标选择。

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

这是一个具有负宽度高度的矩形(例如,宽度=-200高度=-100

如您所见,在这种情况下,left/top 并不等于 x/y

然而在实践中,elem.getBoundingClientRect() 始终返回正宽度/高度,在此我们仅提及负宽度/高度,以便您理解为什么这些看似重复的属性实际上并非重复。

Internet Explorer:不支持 x/y

由于历史原因,Internet Explorer 不支持 x/y 属性。

因此,我们可以制作一个填充(在 DomRect.prototype 中添加 getter),或者仅使用 top/left,因为对于正宽度/高度,它们始终与 x/y 相同,尤其是在 elem.getBoundingClientRect() 的结果中。

坐标 right/bottom 与 CSS 位置属性不同

窗口相对坐标与 CSS position:fixed 之间存在明显的相似之处。

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

如果我们只看上面的图片,我们就可以看到在 JavaScript 中并非如此。所有窗口坐标都是从左上角开始计算的,包括这些坐标。

elementFromPoint(x, y)

document.elementFromPoint(x, y) 的调用返回窗口坐标 (x, y) 处最嵌套的元素。

语法为

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

例如,以下代码突出显示并输出当前位于窗口中间的元素的标签

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) 位于可见区域内时才起作用。

如果任何坐标为负或超过窗口宽度/高度,则它将返回 null

如果我们不检查它,则可能会发生以下典型错误

let elem = document.elementFromPoint(x, y);
// if the coordinates happen to be out of the window, then elem = null
elem.style.background = ''; // Error!

用于“固定”定位

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

要在元素附近显示某些内容,我们可以使用 getBoundingClientRect 获取其坐标,然后将 CSS positionleft/top(或 right/bottom)一起使用。

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

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

function createMessageUnder(elem, html) {
  // create message element
  let message = document.createElement('div');
  // better to use a css class for the style here
  message.style.cssText = "position:fixed; color: red";

  // assign coordinates, don't forget "px"!
  let coords = elem.getBoundingClientRect();

  message.style.left = coords.left + "px";
  message.style.top = coords.bottom + "px";

  message.innerHTML = html;

  return message;
}

// Usage:
// add it for 5 seconds in the document
let message = createMessageUnder(elem, 'Hello, world!');
document.body.append(message);
setTimeout(() => message.remove(), 5000);

单击按钮以运行它

该代码可以修改为在左侧、右侧、下方显示消息,应用 CSS 动画以“淡入”显示消息,等等。这很容易,因为我们拥有该元素的所有坐标和大小。

但请注意一个重要细节:当页面滚动时,消息会远离按钮。

原因很明显:消息元素依赖于 position:fixed,因此当页面滚动时,它会停留在窗口的同一位置。

要更改此设置,我们需要使用基于文档的坐标和 position:absolute

文档坐标

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

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

我们可以使用 position:absolutetop/left 将某个内容放置在文档的特定位置,以便它在页面滚动期间停留在该位置。但我们首先需要正确的坐标。

没有标准方法可以获取元素的文档坐标。但编写该方法很容易。

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

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

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

// get document coordinates of the element
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 函数

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

摘要

页面上的任何点都有坐标

  1. 相对于窗口 – elem.getBoundingClientRect()
  2. 相对于文档 – elem.getBoundingClientRect() 加上当前页面滚动量。

窗口坐标非常适合与 position:fixed 一起使用,而文档坐标非常适合与 position:absolute 一起使用。

这两个坐标系各有优缺点;有时我们需要其中一个,就像 CSS position absolutefixed 一样。

任务

重要性:5

在下面的 iframe 中,您可以看到带有绿色“字段”的文档。

使用 JavaScript 查找箭头所指角的窗口坐标。

文档中实现了一个小功能以方便操作。在任何地方单击都会显示那里的坐标。

您的代码应使用 DOM 获取以下窗口坐标

  1. 左上角,外部角(很简单)。
  2. 右下角,外部角(也很简单)。
  3. 左上角,内部角(有点难)。
  4. 右下角,内部角(有多种方法,选择一种)。

您计算的坐标应与鼠标单击返回的坐标相同。

P.S. 如果元素具有其他大小或边框(不受任何固定值约束),代码也应起作用。

为任务打开沙箱。

外部角

外部角基本上是我们从 elem.getBoundingClientRect() 获得的内容。

左上角坐标 answer1 和右下角坐标 answer2

let coords = elem.getBoundingClientRect();

let answer1 = [coords.left, coords.top];
let answer2 = [coords.right, coords.bottom];

左上内部角

它与外部角不同,在于边框宽度。获取距离的可靠方法是 clientLeft/clientTop

let answer3 = [coords.left + field.clientLeft, coords.top + field.clientTop];

右下内部角

在我们的例子中,我们需要从外部坐标中减去边框大小。

我们可以使用 CSS 方式

let answer4 = [
  coords.right - parseInt(getComputedStyle(field).borderRightWidth),
  coords.bottom - parseInt(getComputedStyle(field).borderBottomWidth)
];

另一种方法是将 clientWidth/clientHeight 添加到左上角的坐标。这可能更好

let answer4 = [
  coords.left + elem.clientLeft + elem.clientWidth,
  coords.top + elem.clientTop + elem.clientHeight
];

在沙箱中打开解决方案。

重要性:5

创建一个函数 positionAt(anchor, position, elem),该函数根据 positionelem 定位在 anchor 元素附近。

position 必须是一个字符串,包含 3 个值中的任何一个

  • "top" – 将 elem 定位在 anchor 正上方
  • "right" – 将 elem 定位在 anchor 正右侧
  • "bottom" – 将 elem 定位在 anchor 正下方

它在任务源代码中提供的函数 showNote(anchor, position, html) 中使用,该函数创建一个具有给定 html 的“注释”元素,并将其显示在 anchor 附近的给定 position 处。

以下是注释的演示

为任务打开沙箱。

在此任务中,我们只需要准确计算坐标即可。有关详细信息,请参阅代码。

请注意:元素必须在文档中才能读取 offsetHeight 和其他属性。隐藏(display:none)或超出文档元素的元素没有大小。

在沙箱中打开解决方案。

重要性:5

修改 上一个任务 的解决方案,以便注释使用 position:absolute 而不是 position:fixed

这将防止页面滚动时注释“脱离”元素。

将该任务的解决方案作为起点。要测试滚动,请添加样式 <body style="height: 2000px">

解决方案实际上非常简单

  • 在 CSS 中为 .note 使用 position:absolute 而不是 position:fixed
  • 使用章节 坐标 中的函数 getCoords() 来获取相对于文档的坐标。

在沙箱中打开解决方案。

重要性:5

扩展上一个任务 在元素附近显示注释(绝对):教函数 positionAt(anchor, position, elem)elem 插入 anchor 内部。

position 的新值

  • top-outright-outbottom-out - 与之前的工作方式相同,它们将 elem 插入 anchor 的上方/右侧/下方。
  • top-inright-inbottom-in - 将 elem 插入 anchor 内部:将其粘贴到上/右/下边缘。

例如

// shows the note above blockquote
positionAt(blockquote, "top-out", note);

// shows the note inside blockquote, at the top
positionAt(blockquote, "top-in", note);

结果

作为源代码,采用任务 在元素附近显示注释(绝对) 的解决方案。

教程地图

评论

在评论之前阅读此内容...
  • 如果您有改进建议 - 请 提交 GitHub 问题 或拉取请求,而不是发表评论。
  • 如果您无法理解文章中的某些内容 - 请详细说明。
  • 要插入几行代码,请使用 <code> 标签,对于多行代码,请将其包装在 <pre> 标签中,对于 10 行以上的代码,请使用沙盒 (plnkrjsbincodepen…)