元素大小与位置

总览

Pasted image 20240428170934.png

Pasted image 20240428170954.png

offset

Pasted image 20240428180141.png

Pasted image 20240428180230.png

client

Pasted image 20240428175620.png

Pasted image 20240428180317.png

scroll

Pasted image 20240428185034.png

Pasted image 20240428185000.png

窗口大小与位置

窗口(视口)宽高

使用 document.documentElement.clientWidth/clientHeight

Pasted image 20240428192331.png

而不是 window.innerWidth/innerHeight,因为其包含滚动条

在大多数情况下,我们需要可用的窗口宽度以绘制或放置某些东西

文档的宽高

从理论上讲,可以使用 document.documentElement.scrollWidth/scrollHeight 来测量文档的完整大小,但由于历史原因,这无法正常工作。

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

const scrollHeight = Math.max(
  document.body.scrollHeight, document.documentElement.scrollHeight,
  document.body.offsetHeight, document.documentElement.offsetHeight,
  document.body.clientHeight, document.documentElement.clientHeight
)

const scrollWidth = Math.max(
  document.body.scrollWidth, document.documentElement.scrollWidth,
  document.body.offsetWidth, document.documentElement.offsetWidth,
  document.body.clientWidth, document.documentElement.clientWidth
)

获得当前滚动

滚动

滚动方法

window

// 该方法没有任何意义,且有 BUG:在上一次方法动画没执行完前再次调用,页面就会乱动
// 请使用 window.scrollTo({ left: number; top: number; behavior: "smooth" })

/**
 * @description: 平滑滚动文档到指定位置
 * @param {number} x 滚动到的 X 轴坐标
 * @param {number} y 滚动到的 Y 轴坐标
 */
function smoothScrollTo(x, y) {
  // 当前位置
  let scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft
  let scrollTop = document.documentElement.scrollTop || document.body.scrollTop

  function _step() {
    // 距离目标距离
    const distanceX = x - scrollLeft
    const distanceY = y - scrollTop
    // 目标位置
    scrollLeft = scrollLeft + distanceX / 5
    scrollTop = scrollTop + distanceY / 5

    if (Math.max(Math.abs(distanceX), Math.abs(distanceY)) < 1) {
      window.scrollTo(x, y)
    } else {
      window.scrollTo(scrollLeft, scrollTop)
      requestAnimationFrame(_step)
    }
  }

  _step()
}

elem

禁止滚动

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

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

解决方法是对比冻结前后的 clientWidth。如果它增加了(滚动条消失后),就在 document.body 滚动条原来的位置处添加 padding。保持了滚动条冻结前后文档内容宽度相同

坐标

两种坐标系

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

  1. 相对于窗口:类似于 position: fixed,从窗口的顶部/左侧边缘计算得出
  2. 相对于文档:与文档根(document root)中的 position: absolute 类似,从文档的顶部/左侧边缘计算得出

Pasted image 20240429105404.png

当文档滚动了:

获取元素大小和窗口坐标

elem.getBoundingClientRect() 方法返回一个 DOMRect 对象,提供了元素的最小矩形大小及相对于==窗口(视口)==的位置。

主要的 DOMRect 属性:

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

Pasted image 20240429110100.png

x/y 和 width/height 对矩形进行了完整的描述。可以很容易地从它们计算出派生(derived)属性:

为什么需要派生(derived)属性:

注意:

通过窗口坐标获取元素

document.elementFromPoint(x, y) 方法返回在窗口坐标 (x, y) 处嵌套最多/深(the most nested)的元素(z-index 最大的元素)

注意:

获取文档坐标

// 获取元素的文档坐标
function getCoords(elem) {
  const box = elem.getBoundingClientRect()

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

注意

读取几何信息可能导致回流

Reflow

不要从 CSS 中获取宽高

不要使用像 getComputedStyle 这样的 API 获取元素宽高

原因:

常见问题与解决方案

判断元素是否滚动到底

scrollTop 是一个非整数,而 scrollHeight 和 clientHeight 是四舍五入的,因此确定滚动区域是否滚动到底的唯一方法是查看滚动量是否足够接近某个阈值(在本例中为 1):

Math.abs(element.scrollHeight - element.clientHeight - element.scrollTop) < 1

判断元素是否能滚动

window.getComputedStyle(element).overflowY === "visible"
window.getComputedStyle(element).overflowY !== "hidden"

在某元素附近展示内容

// 在 elem 下显示 html 内容
function createMessageUnder(elem, html) {
  let message = document.createElement('div')
  // 在这里最好使用 CSS class 来定义样式
  message.style.cssText = "position: fixed; color: red"

  // 分配坐标
  let coords = elem.getBoundingClientRect()
  message.style.left = coords.left + "px"
  message.style.top = coords.bottom + "px"

  message.innerHTML = html

  return message
}

// 使用:
let message = createMessageUnder(elem, 'Hello, world!')
document.body.append(message)
setTimeout(() => message.remove(), 5000)

上面方法使用的是相对于窗口的坐标(getBoundingClientRect),页面滚动会导致“分离”

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

// 在 elem 下显示 html 内容
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
}

其中 getCoords 方法 见上