判断一个元素是否在可视区域中

156 阅读3分钟

基础知识

Element.clientHeight & Element.clientWidth

image.png

  • clientWidth:元素内容区宽度加上左右内边距宽度,即clientWidth = content + padding
  • clientHeight:元素内容区高度加上上下内边距高度,即clientHeight = content + padding

offsetTop

offsetTop 是 JavaScript 中一个用于获取元素相对于其包含元素的顶部位置的属性。它返回元素的上边界到其包含元素(offset parent)的上边界的距离,以像素为单位。

demo.png

注意事项

  • 滚动offsetTop 不会受到滚动的影响。如果包含元素或其祖先元素有滚动条,offsetTop 返回的值仍然是元素相对于包含元素的初始位置。
  • 包含元素:包含元素(offset parent)通常是离元素最近的已定位祖先元素。如果没有已定位祖先元素,则为 <body> 元素。

scrollTop

  • scrollWidthscrollHeight用于获取一个元素的完整内容的宽度和高度,包括由于溢出而不可见的部分。这两个属性通常用于带有滚动条的元素,来测量内容的总尺寸,当内容动态变化时,scrollWidthscrollHeight 也会相应地变化。
  • scrollTop用来获取或设置元素的垂直滚动位置。它返回一个整数值,表示元素的内容顶部被卷起来的像素值。

demo.png

getBoundingClientRect

image.png

3种方法

1. el.offsetTop - document.documentElement.scrollTop <= viewPortHeight

demo.png

使用 el.offsetTop - document.documentElement.scrollTop <= viewPortHeight 判断一个元素是否在可视区域中有几个潜在的问题:

  1. 只考虑了元素的顶部边界:该公式只考虑了元素的顶部边界是否在可视区域内,没有考虑元素的底部边界。这意味着如果元素非常高,只有一部分在可视区域内,该公式仍然会返回 true
  2. 未考虑负值情况:当元素的顶部边界在视口上方时,该公式不会处理这种情况,因为 offsetTop - scrollTop 可能会是负值。

为了更准确地判断一个元素是否在可视区域中,我们可以使用以下方法:

解决方案

我们可以使用以下方法来判断一个元素是否在可视区域中,包括考虑元素的顶部和底部边界:

  1. 计算元素的位置

    • 元素的顶部边界相对于视口的位置:el.offsetTop - document.documentElement.scrollTop
    • 元素的底部边界相对于视口的位置:el.offsetTop + el.offsetHeight - document.documentElement.scrollTop
  2. 判断顶部和底部边界是否在可视区域中

    • 元素的顶部边界在视口下方(大于等于 0)。
    • 元素的底部边界在视口上方(小于等于视口高度)。

事例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>isInViewPort Example</title>
    <style>
        .container {
            height: 1500px;
            background: linear-gradient(to bottom, red, yellow, green, blue, purple);
        }
        .box {
            position: absolute;
            top: 800px;
            width: 100px;
            height: 100px;
            background-color: black;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="box" id="box"></div>
    </div>
    <script>
        function isInViewPort(el) {
            const viewPortHeight = window.innerHeight || document.documentElement.clientHeight;
            const scrollTop = document.documentElement.scrollTop;
            const elementTop = el.offsetTop - scrollTop;
            const elementBottom = el.offsetTop + el.offsetHeight - scrollTop;

            return elementTop < viewPortHeight && elementBottom > 0;
        }

        var box = document.getElementById('box');
        window.addEventListener('scroll', function() {
            if (isInViewPort(box)) {
                console.log('Box is in the viewport');
            } else {
                console.log('Box is not in the viewport');
            }
        });
    </script>
</body>
</html>

2. 利用getBoundingClientRect获取DOMRect对象

demo.png

如果一个元素在视窗之内的话,那么它一定满足下面两个条件:

  • top为参照点:top >= 0 && top <= viewPortHeight
  • bottom为参照点:bottom >= 0 && bottom <= viewPortHeight

demo

<div class="content"></div>
<div class="box" id="box"></div>
.content {
  height: 2000px;
  background: linear-gradient(to bottom, red, yellow, green, blue, purple);
}
.box {
  width: 100px;
  height: 100px;
  background: black;
  position: absolute;
  top: 1500px;
}
window.addEventListener("scroll", function () {
  const box = document.getElementById("box");
  const inView = isInViewPortOfOne(box);
  console.log("Is in viewport:", inView);
});

function isInViewPortOfOne(el) {
  const viewPortHeight =
    window.innerHeight ||
    document.documentElement.clientHeight ||
    document.body.clientHeight;
  const rect = el.getBoundingClientRect();
  const top = rect.top;
  const bottom = rect.bottom;
  return (
    (top >= 0 && top <= viewPortHeight) ||
    (bottom >= 0 && bottom <= viewPortHeight)
  );
}

3. Intersection Observer

参考:1. IntersectionObserver;2. IntersectionObserverEntry

Intersection Observer 即重叠观察者,从这个命名就可以看出它用于判断两个元素是否重叠,因为不用进行事件的监听,性能方面相比getBoundingClientRect会好很多

使用步骤主要分为两步:创建观察者和传入被观察者

// 创建 Intersection Observer 实例
const observer = new IntersectionObserver(
  (entries, observer) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        console.log("Box is in the viewport!");
      } else {
        console.log("Box is out of the viewport!");
      }
    });
  },
  {
    root: null, // 视口为根元素
    rootMargin: "0px",
    threshold: 0.1, // 元素进入视口的阈值
  }
);

// 选择要观察的元素
const box = document.getElementById("box");
observer.observe(box);