前言
在项目开发中,往往需要去判断一个元素是否被用户看见(进入可视区
),而这是为了去实现某些目的,例如:
- 优化资源加载、渲染
- 图片懒加载
- 无限滚动(数据分批渲染)
- 虚拟列表
- …
- 添加动画效果
- 对于进入可视区的元素添加动画,让页面更具有 交互感
- 判断广告曝光
- 用于去判断页面中的广告点位是否被曝光
- …
之前的做法就是通过监听 scroll
事件,然后再事件 回调 中调用 目标元素 的getBoundingClientRect()
方法,获取其相对于 视口左上角的坐标,从而判断其是否存在于可视区中。
function isElementInViewport(element) {
const rect = element.getBoundingClientRect();
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
const isInVerticalViewport = rect.top <= viewportHeight && rect.bottom >= 0;
const isInHorizontalViewport = rect.left <= viewportWidth && rect.right >= 0;
return isInVerticalViewport || isInHorizontalViewport;
}
window.addEventListener('scroll', () => {
const inViewport = isElementInViewport(document.querySelector('.target-item'));
...
});
但上述方式存在由于 滚动事件频发 导致频繁调用元素的 getBoundingClientRect()
进而引发页面的 重排或回流,而现在通过 IntersectionObserver 可以更便捷的实现这个功能,避免造成性能问题。
IntersectionObserver
元素是否可见,实际上就是 目标元素 与 可视区 是否产生了 交叉区,因此 IntersectionObserver
也被称为 “交叉观察器”。
如下是其简单的一个使用:
const isView = ref(false);
onMounted(() => {
const intersectionObserver = new IntersectionObserver((entries) => {
console.log(1111);
isView.value = entries[0].intersectionRatio > 0;
},{
root: document.querySelector('.box')!,
});
intersectionObserver.observe(document.querySelector('.item_10')!);
});
通过这个例子我们来简单介绍下其相关的配置 new IntersectionObserver(callback[, options])
,更具体可见 此处。
callback 回调函数
执行时机
通过上述例子不难发现,其回调函数的执行时机 默认只有两次:
- 目标元素
进入视口
- 目标元素
离开视口
这也是为什么前面说使用 IntersectionObserver API
比之前在 scroll
事件 回调中判断目标元素是否进入视口性能更好的原因之一。
但实际上我们还可以通过 options.threshold 配置项来设定其更具体的触发时机,后面会提到。
接收参数
var observer = new IntersectionObserver((entries, observer) => {...});
options 配置项
var observer = new IntersectionObserver(callback, {
root: document.querySelector('.container'),
rootMargin: '0px 0px 0px 0px',
threshold: [0.25, 0.5, 0.75, 1]
});
-
root
- 传入监听
目标元素的祖先元素
,会将传入的元素作为视口元素
,如果没有指定默认就为整个页面视口
- 传入监听
-
rootMargin
- 顾名思义,实际上就相当于 CSS 的
margin
属性,可以放大或缩小 视口元素 的判定范围,默认值是"0px 0px 0px 0px"
- 例如,
0px 0px -100px 0px
相当于 视口元素 下边缘向上 收缩100px
,此时需要 目标元素顶部 进入可视区域>= 100px
才会触发回调
- 顾名思义,实际上就相当于 CSS 的
-
threshold
- 能够控制回调函数的触发时机,实际上它指定了 目标元素 与 视口元素 之间的 交叉比例,是
0.0
到1.0
之间的数组 - 例如,
[0.25, 0.5, 0.75, 1]
相当于指定了目标元素
与视口元素
交叉比例达到25%、50%、75%、100%
时各触发一次回调函数
- 能够控制回调函数的触发时机,实际上它指定了 目标元素 与 视口元素 之间的 交叉比例,是
实例方法
应用场景
常见的图片懒加载、无限滚动、虚拟列表等这里就不再实现,我们来看一些其他的场景吧。
标题与内容的联动
很多站点都会有这样的功能,以 Vue 文档 为例,如下:
如果你有兴趣去查看一下其实现,会发现其用的就是 IntersectionObserver:
具体实现
观察上述效果,不难看出就是一个双向交互:
于是可以很容易就写出如下效果:
<template>
<div class="title-box">
<div class="active-line" :style="{ top: activeOffsetTop + 'px' }">div>
<a
:class="[
'title-item',
`title-item-${item.id}`,
item.active ? 'active' : '',
]"
:href="`#${item.id}`"
v-for="(item, i) in data"
:key="item.id"
@click="clickAction($event, item.id)"
>{{ item.title }}
div>
<div class="content-box">
<div class="content-item" v-for="item in data" :data-id="item.id">
<h3 :id="item.id">{{ item.title }}h3>
<div class="content-item-text" v-for="text in item.content">
{{ text }}
div>
div>
div>
template>
<script setup lang="ts">
...
const data = ref([
{
id: "1",
title: "标题 1",
content: ["内容 1".repeat(100)],
active: true,
},
...
]);
let intersectionObserver: IntersectionObserver;
const observe = () => {
const root = document.querySelector(".content-box")!;
const contents: HTMLElement[] = [] = Array.from(document.querySelectorAll(".content-item")!);
intersectionObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
const { target, isIntersecting } = entry;
if (isIntersecting) {
const id = target.dataset.id;
clickAction(
{ target: document.querySelector(`.title-item-${id}`), mock: true },
id
);
}
});
},
{
root,
threshold: [0.5],
}
);
contents.forEach((target) => {
intersectionObserver.observe(target);
});
};
onMounted(() => {
observe();
});
onBeforeUnmount(() => {
intersectionObserver.disconnect();
});
const activeOffsetTop = ref(0);
const clickAction = (e, id: string) => {
data.value.forEach((v) => {
if (id === v.id) {
v.active = true;
activeOffsetTop.value = e.target.offsetTop;
} else {
v.active = false;
}
});
};
script>
存在缺点
基于 IntersectionObserver 实现很便捷,但也会存在一个缺点,如下所示:
当点击 还有其他问题?
标题时,选中样式不会在此标题上,而是选中了它下面的那个标题 选择你的学习路径
,这是为啥呢?
这是因为 还有其他问题?
标题对应的内容很短,于是在基于 a 标签
锚点跳转的时候,将它下面标题的内容也带入了 可视区 中,此时 IntersectionObserver 的回调就会触发,所以最终选中的标题就是 选择你的学习路径
。
入场动画
IntersectionObserver API 的特性非常适合用来处理元素的 入场动画
,很多官网的动画也是这么去实现的,那下面就实现一个简单的入场动画,如下:
Element.animate()
这里使用 Element.animate()
方法来执行上面这个简单的动画,针对一些较为复杂的动画可以通过 @keyframes + animation
的方式定义在具体的 选择器 中,然后在目标元素进入视口时为其添加对应的 选择器 即可。
Element.animate() 方法接收两个参数:
keyframes
,关键帧对象数组或一个关键帧对象,关键帧格式options
,代表动画持续时间的整数(以毫秒为单位),或者一个包含一个或多个时间属性,KeyframeEffect() 参数
看着很抽象,实际上就是把之前在 CSS 写动画的方式抽离到 JS 中了。
具体实现
<template>
<div class="list">
<div class="item" v-for="i in 20" :data-index="i">{{ i }}div>
div>
template>
兼容性
从下图可以看出各主流浏览器都有相应的实现,针对一些兼容性比较差的可以使用对应的 polyfill。
最后
IntersectionObserver API 提供给我们一种更便捷的判断目标元素是否处于可视区的方式,它相对于传统滚动事件的实现方式来讲 性能更好
,除此之外还允许我们自定义 thresholds
阈值 来实现 自定义回调触发时机
,还可以实现 同时监听多个目标元素
。
除此之外,也要注意在 快速滚动 的场景下 IntersectionObserver
可能会不执行的问题,详情可见。
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.shuli.cc/?p=20185,转载请注明出处。
评论0