现代 JavaScript 语言教程:坐标系统

zhangbao 发布于 4个月前

在 JavaScript 中,元素是在一个坐标系统里移动的。这些操作元素移动的方法包含在两类坐标系统中:

  1. 相对于窗口(或另一个视口)的 top/left。
  2. 相对于文档的 top/left。

理解两类坐标系统的不同很重要。

窗口坐标:getBoundingClientRect

窗口坐标系统的坐标原点(0,0)在窗口的左上角。

elem.getBoundingClientRect 方法返回元素 elem 在窗口坐标系统中的坐标信息,是一个对象,包含以下属性:

  1. top:元素顶部距离窗口顶部的距离。
  2. left:元素左边缘距离窗口左边缘的距离。
  3. right:元素右边缘距离窗口左边缘的距离。
  4. bottom:元素底部距离窗口顶部的距离。

这些属性值都是相对于窗口左上角计算的。

如果滚动了页面,在 elem 再次使用 getBoundingClientRect 方法时,结果就不同了。

而且:

  • 坐标点的值可能是个小数,不过并不影响 style.position.left/top 的设置。
  • 坐标点的值可能是个负值。例如,我们滚动页面,元素滚动到了页面顶部上面,elem.getBoundingClientRect().top 的结果就是负值。
  • 一些浏览器(像 Chrome)中 getBoundingClientRect 方法返回的结果中,还包含 widthheight 属性。当然对于不支持这两个属性的浏览器,我们可以使用变通的方法:height=bottom-topwidth=right-left

elementFromPoint(x, y)

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。所以使用时不检查,可能会出错的。

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

position: fixed

许多时候,我们需要用坐标来固定某个元素在窗口中某个位置不动。在 CSS 中,元素相对视口定位要使用 position: fixed,它与 left/top(或者 right/bottom)相互配合,就能达到固定位置的效果。

比如,我们先使用 getBoundingClientRect 获取元素的窗口坐标,然后在元素附近显示一些信息。

下面的 createMessageUnder(elem, html) 就实现了在 elem 下显示 html 的内容:

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

但这种显示信息的方式有问题,当你滚动页面时,会发现元素 elem 会跟随滚动,但是信息不动。因为 message 是使用 position:fixed 相对窗口定位的。

为了修正这个问题,我们需要使用基于文档坐标系统定位的 position:absolute 属性。

文档坐标

基于文档坐标系统定位是指以文档左上角为坐标原点(0,0)定位,而不是窗口的。

在 CSS 中,窗口坐标定位对应 position:fixed,文档坐标定对应 position:absolute

我们可以使用 position:absolute 结合 top/left 将元素定位在文档的某个位置,这样即使页面滚动,因为元素相对文档定位,所以元素在文档中的位置始终是不变的。

为了说明清楚,我们用窗口坐标 (clientX, clientY) 和 文档坐标 (pageX, pageY) 来解释。

当页面没有滚动的时候,窗口坐标 (clientX, clientY) 和文档坐标 (pageX, pageY) 重合,因为它们的坐标原点是重合的。

如果我们滚动窗口,(clientX, clientY) 就改变了,因为这个坐标是相对窗口坐标系统计算的,但是 (pageX, pageY) 保持不变。

下图是在页面垂直滚动一段距离后的信息:

  • 标题《From today's featured article》的 clientY 的值为 0,因为标题元素处于窗口顶端。
  • clientX 的值没有改变,因为我们没有水平滚动。
  • (pageX, pageY) 坐标是始终保持不变的,因为这是元素相对于文档的坐标。

获得文档坐标

关于获得元素文档坐标的方法,现在还没有一个标准的。但是可以很容易通过变通的方法实现:

  • pageY = clientY + 文档垂直滚动的距离。
  • pageX = clientX + 文档水平滚动的距离。

下面我们来封装一个方法 getCoords(elem),利用 getCoords(elem) 加上当前页面的滚动距离,计算出元素的文档坐标。

// get document coordinates of the element
function getCoords(elem) {
  let box = elem.getBoundingClientRect();

  return {
    top: box.top + window.pageYOffset,
    left: box.left + window.pageXOffset
  };
}

总结

页面中任何点都有坐标:

  • 相对于窗口:elem.getBoundingClientRect()
  • 相对于文档:elem.getBoundingClientRect() 加上当前页面的滚动距离。

positon: fixed 实现元素相对于窗口坐标系统的定位;positon: absolute 实现元素相对于文档坐标系统的定位。


登录参与评论