如何判断元素在可视页面内

目前判断元素出现在可视区域的有三种方案, 我们来逐一讲解:

(1)offsetTop, scrollTop

(2)getBoundingClientRect

(3)IntersectionObserver

一、方法一:offsetTop + scrollTop

(1)offsetTop: 当前元素相对于其 offsetParent 元素的顶部内边距的距离。

offsetParent: 指向最近的包含该元素的定位元素或者最近的 table, td, th, body 元素。

offsetWidth, offsetHeight包括了content, padding, border, 不包括margin
clientWidth, clientHeight包括了content, padding 不包括border, margin

(2)scrollTop: 元素的内容顶部到它的视口可见内容的顶部的距离的度量。

(3)实现如下:

由图可知, 考虑纵轴方向的话。

要么元素不在可视区域下方, 此时应该满足 el.offsetTop < document.documentElement.scrollTop + document.documentElement.clientHeight。

要么元素不在可视区域上方, 此时应该满足 el.offsetTop > document.documentElement.scrollTop - el.offsetHeight。

横向方向同理(此处忽略),代码实现如下

const isInView = (el) => {
  const documentScrollTop = document.documentElement.scrollTop;
  const documentClientHeight = document.documentElement.clientHeight;
  if (el.offsetTop > documentScrollTop + documentClientHeight) {
    console.log('元素在可视区域下方');
  } else if (el.offsetTop < documentScrollTop - el.offsetHeight) {
    console.log('元素在可视区域上方');
  } else {
    console.log('在可视区域啦');
  }
}
const scrollEvent = throttle(isInView);
document.addEventListener('scroll', () => {
  scrollEvent(targetNode);
})

二、方法二:getBoundingClientRect

(1)定义:Element.getBoundingClientRect() 返回一个 DOMRect对象,其提供了元素的大小及其相对于视口的位置。

(2)熟悉API。

对于该盒子, position left是220, top是100, margin设置了10, border设置了10, padding设置了20, width和height都是200。 此时打印出来的DOMReact对象如何所示。

其width打印出来为260, 实际上是width + padding + border的值, height同理。

其y打印出来为110, 实际上是top + margin的值, x同理。

其bottom打印出来为370, 实际上是y + width的值(其实就是不算margin-bottom的值)

此处为MDN的图。

因为他的x, y, bottom, left都是相对于视图窗口而言, 故实现起来也很方便。

要让元素不在窗口上方, 则 bottom 应该大于等于 0。

要让元素不在窗口下方, 则 y 应该小于等于视图高度。

横向同理, top应该大于等于0, x应该小于等于视图宽度。

代码如下:

const targetNode = document.querySelector('.child');
const isInView = (el) => {
  const documentClientHeight = document.documentElement.clientHeight;
  const { bottom, y } = el.getBoundingClientRect();
  console.log(bottom, y);
  if (bottom < 0) {
    console.log('元素在可视区域上方')
  } else if (y > documentClientHeight) {
    console.log('元素在可视区域下方')
  } else {
    console.log('在可视区域啦')
  }
}
const scrollEvent = throttle(isInView);
document.addEventListener('scroll', () => {
  scrollEvent(targetNode);
})

三、方法三:IntersectionObserver

(1)定义: IntersectionObserver 接口(从属于 Intersection Observer API)提供了一种异步观察目标元素与其祖先元素或顶级文档视口(viewport)交叉状态的方法。其祖先元素或视口被称为根。

(2)当一个 IntersectionObserver 对象被创建时,其被配置为监听根中一段给定比例的可见区域。一旦 IntersectionObserver 被创建,则无法更改其配置,所以一个给定的观察者对象只能用来监听可见区域的特定变化值。

按照我们前面两种方法,其实都需要去频繁调用getBoundingRect(), scrollTop()方法等, 这些方法都会导致回流重绘且都在主线程上运行, 因此频繁触发、调用可能会造成性能问题。这种检测方法极其怪异且不优雅。
Intersection Observer API 会注册一个回调函数,每当被监视的元素进入或者退出另外一个元素时 (或者 viewport),或者两个元素的相交部分大小发生变化时,该回调方法才会被触发执行。
其实就是浏览器提供了对应的方法, 让主线程不需要再为了监听元素相交而辛苦劳作,浏览器会自行优化元素相交管理。

(3)熟悉API。

可以通过new IntersectionObserver(callback, options)实例化一个observe对象。

options中可以通过root属性指定root元素, 通过threshold属性指定交集触发回调的阈值。

let options = {
 root: document.querySelector("#scrollArea"),
 rootMargin: "0px",
 threshold: 1.0,
};

创建一个 observer 后需要给定一个目标元素进行观察。

const targetNode = document.querySelector('.child');
const intersectionObserver = new IntersectionObserver((entries) => {
  console.log(entries);
})
intersectionObserver.observe(targetNode);

我们通过intersectionRatio去判断即可, 如果小于等于0, 则说明已经在视野外了。

代码如下:

const targetNode = document.querySelector('.child');
const intersectionObserver = new IntersectionObserver((entries) => {
  if (entries[0].intersectionRatio <= 0) {
   console.log('视野之外')
  } else {
  console.log('视野之内');
  }
})
intersectionObserver.observe(targetNode);

注册的回调函数将会在主线程中被执行。所以该函数执行速度要尽可能的快。如果有一些耗时的操作需要执行,建议使用 Window.requestIdleCallback() 方法。

四、兼容性

(1)对于第一种方法都是基本API了。

(2)对于第二种也基本兼容了。

(3)对于第三种, 兼容性相对来说就要差一些了。

版权声明:来自网友好好吃饭好好睡觉,版权归原作者所有

原文链接:https://www.webppp.com/view/yuan_su_zai_ke_shi_ye_mian_nei.html