当前位置:网站首页>IntersectionObserver交叉观察器
IntersectionObserver交叉观察器
2022-07-28 12:49:00 【Maic】
交叉观察器 IntersectionObserver
可以观察
元素是否可见,由于目标元素与视口产生一个交叉区,我们可以观察到目标元素的可见区域,通常称这个API为交叉观察器
前段时间内部系统业务需要,用 IntersectionObserver实现了table中的上拉数据加载,如果有类似需求,希望本文能带给你一点思考和帮助
正文开始...
vite初始化一个项目
参考官网vite[1]快速启动一个项目
$ npm init [email protected]
选择一个vue模板快速初始化一个页面后,我们添加路由页面
npm i [email protected]
在已有项目上添加路由
// main.ts
import { createApp } from 'vue'
import route from './router/index';
import App from './App.vue'
const app = createApp(App);
app.use(route);
app.mount('#app');
修改App模板,另外我们引入elementPlus,引入它主要是我们在实际项目中,我们用第三方UI库非常高频,在之前一篇文章中有提到虚拟列表优化大数据量,具体参考测试脚本把页面搞崩了。今天用交叉观察器也算是优化大数据量渲染的一种方案。
// App.vue
<script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
import { ElConfigProvider } from "element-plus";
import { ref } from "vue";
const zIndex = ref(1000);
const size = ref("small");
</script>
<template>
<el-config-provider :size="size" :z-index="zIndex">
<router-view></router-view>
</el-config-provider>
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
创建router文件夹,新建index.ts,添加路由页面
// router/index.ts
import { createWebHashHistory, createRouter } from 'vue-router';
import HelloWorld from '../components/HelloWorld.vue'
import ShopListPage from '../view/shopList/Index.vue';
const routes = [
{
path: '/hello',
component: HelloWorld
},
{
path: '/',
component: ShopListPage
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router;
我们新建一个view/shopList目录,在shopList中新建一个Index.vue开始今天的栗子。
本地开发环境安装mockjs模拟接口数据
npm i mockjs --save-dev
新建mock我们使用它模拟接口随机数据,我们会在main.ts引入该mock/index.js
// mock/index.ts
import Mockjs from 'mockjs';
import mockFetch from 'mockjs-fetch';
// 拦截mock
mockFetch(Mockjs);
// 生成随机长度的数组
const createMapRandom = (len: number) => {
const data = new Array(len);
return data.fill('Maic')
}
Mockjs.mock('\/shoplist\/list.json', () => {
return {
code: 0,
data: Mockjs.mock({
'list|10': [{
'id|+1': createMapRandom(10).map(() => Mockjs.mock('@id')),
'adress|1': createMapRandom(10).map(() => Mockjs.mock('@city')),
"age|1": createMapRandom(10).map(() => Mockjs.mock('@integer(0,100)')),
'name|1': createMapRandom(10).map(() => Mockjs.mock("@cname")),
}
]
})
}
});
注意我们在使用mockjs时,我们使用了另外一个库mockjs-fetch,如果在项目中使用fetch做ajax请求,那么必须要使用这个库拦截mock请求,在默认情况下,如果你使用的是axios库,那么mock会默认拦截请求。
在view/shopList目录下,我们创建Index.vue
<template>
<div class="shopList">
<h3>intersectionObserver交叉器实现上拉加载</h3>
<el-table :data="tableData" border stripe style="width: 100%">
<el-table-column type="index" width="50" />
<el-table-column property="id" label="id" width="180" />
<el-table-column property="name" label="Name" width="180" />
<el-table-column property="adress" label="Address" />
<el-table-column property="age" label="Age" />
</el-table>
<div @click="handleMore" v-if="hasMore">点击加载更多</div>
<div v-else>没有数据啦</div>
</div>
</template>
对应的js,这段js逻辑非常简单,就是请求模拟的mock数据,然后设置table所需要的数据,点击加载更多就继续请求,如果没有数据了,就显示没有数据。
<script setup lang="ts">
import { reactive, ref, onMounted } from "vue";
import { ElTable, ElTableColumn } from "element-plus";
import "element-plus/dist/index.css";
const hasMore = ref(false);
const tableData = ref([]);
const condation = reactive({
pageParams: {
page: 1,
pageSize: 10,
},
});
// TODO 请求数据
const featchList = async () => {
const res = await fetch("/shoplist/list.json", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(condation.pageParams),
});
const json = await res.json();
tableData.value = tableData.value.concat(json.data.list);
};
onMounted(() => {
featchList();
});
// TODO 加载更多
const handleMore = () => {
featchList();
};
</script>
我们用vite初始化的项目是vue3,在vue3的script我们使用了setup,那么我们在script中不再用返回一个对象,申明的方法和变量可以直接在模板中使用,这里与组合式API有点区别,但是从功能上并没有什么区别。
在传统上,我们实现上拉加载,我们会监听滚动条到底部的距离,我们计算滚动条距离顶部位置、浏览器可视区域的高度、body的高度,监听滚动事件,判断scrollTop + clientHeight > bodyScrollHeight,然后就判断是否需要加载下一页。
监听滚动事件,我们会加防抖处理事件,即使这样scroll事件也会高频触发,这样也会影响性能。
因此我们使用IntersectionObserver这个API实现上拉加载。
我们看下IntersectionObserver这个API
// callback是一个回调函数,options是可配置的参数
var observer = new IntersectionObserver(callback, options);
// target1是一个具体的dom元素
observer.observe(target1) // 开始观察
observer.observe(target2)
observer.unobserve(target) // 停止观察
observer.disconnect(); // 停止观察
我们可以在页面中用observer可以观察多个dom,同时我们也需要知道new IntersectionObserver()这个是异步的,并不会随着页面的滚动而时时触发,它只会在线程空闲下来才会执行,因此它在事件循环中,优先级很低,只有等其他任务执行完了,浏览器有了空闲才会执行它。
当目标元素可见时,会触发callback,另一次是当元素完全不可见时也会触发该callback
const options = {};
var observer = new IntersectionObserver(
(entries, observer) => {
console.log(entries);// entries 是一个数组,监听几个dom就会有几个
}
, options);
在IntersectionObserver中的entries第一个参数里,其中有几个参数我们需要了解下
// entries
type clientRect = {
top: number;
bottom: number,
left: number,
right: number,
width: number,
height: number
}
const entriesRes = {
time: 12334,
rootBounds: {
bottom: 920,
height: 1024,
left: 0,
right: 1024,
top: 0,
width: 920
} as clientRect,
boundingClientRect: {
...
} as clientRect,
intersectionRect: {
} as clientRect,
intersectionRatio: 0,
target: dom
};
const entries = [entriesRes]
// observer
{
delay: 0
root: null
rootMargin: "0px 0px 0px 0px"
thresholds: [0]
trackVisibility: false
}
在第二个参数options中可配置参数
var options = {
threshold: [0, 0.5, 1],
root: document.getElementById('box1')
}
threshold这个可以设置目标元素可见范围在0,50%,100%时触发回调callback,root就是可以目标元素所在的祖先节点
我们花了一些时间了解IntersectionObserver这个API,接下来我们用它实现一个上拉加载。
// 关键代码
...
// 自定义一个上拉加载的指令
const vScrollTable = {
created: (el, binding, vnode, prevVnod) => {
handleScrollTable(el, binding);
},
};
然后就是handleScrollTable这个方法
...
// 自定义指令的created中调用该方法
const handleScrollTable = (el, binding) => {
const { infiniteScrollDisable, cb } = binding.value;
// 如果el不存在,则禁止后面IntersectionObserver的实例化
if (!el && !cb) {
return;
}
// 核心上拉加载代码
const intersectionObserver = new IntersectionObserver((enteris, observer) => {
// console.log(enteris, observer);
const [curentEnteris] = enteris;
const { intersectionRatio } = curentEnteris;
// 不可见的时候,禁止加载
if (intersectionRatio <= 0) return;
// 设置一个可以加载更多的开关
if (infiniteScrollDisable) {
cb();
}
});
// 开始监听
intersectionObserver.observe(el);
};
在模板里我们只需在目标元素上绑定指令就行
...
<div
class="load-more-btn"
@click="handleMore"
v-if="hasMore"
v-scroll-table="{ cb: handleMore, infiniteScrollDisable: hasMore }"
>
点击加载更多<el-icon :class="[loading ? 'is-loading' : '']">
<component :is="Loading"></component> </el-icon
>({{ tableData.length }}/{{ total }})
</div>
<div v-else>没有数据啦</div>
我们直接在元素上绑定自定义的指令v-scroll-table="{cb: handleMore,infiniteScrollDisable: hasMore}"就行
完整的全部示例见下面代码
<!--shopList/Index.vue-->
<template>
<div class="shopList">
<h3>intersectionObserver交叉器实现上拉加载</h3>
<el-table
:data="tableData"
border
stripe
style="width: 100%"
v-loading="loading"
>
<el-table-column type="index" width="50" />
<el-table-column property="id" label="id" width="180" />
<el-table-column property="name" label="Name" width="180" />
<el-table-column property="adress" label="Address" />
<el-table-column property="age" label="Age" />
</el-table>
<div
class="load-more-btn"
@click="handleMore"
v-if="hasMore"
v-scroll-table="{ cb: handleMore, infiniteScrollDisable: hasMore }"
>
点击加载更多<el-icon :class="[loading ? 'is-loading' : '']">
<component :is="Loading"></component> </el-icon
>({{ tableData.length }}/{{ total }})
</div>
<div v-else>没有数据啦</div>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, onMounted } from "vue";
import { ElTable, ElTableColumn, ElIcon } from "element-plus";
import { Loading } from "@element-plus/icons-vue";
import "element-plus/dist/index.css";
const hasMore = ref(false);
const tableData = ref([]);
const loading = ref(false);
const condation = reactive({
pageParams: {
page: 1,
pageSize: 10,
},
});
const total = ref(100);
// TODO 请求数据
const featchList = async () => {
const res = await fetch("/shoplist/list.json", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(condation.pageParams),
});
const json = await res.json();
tableData.value = tableData.value.concat(json.data.list);
hasMore.value = true;
if (total.value === tableData.value.length) {
hasMore.value = false; // 没有更多了
}
loading.value = false;
};
onMounted(() => {
featchList();
});
// TODO 加载更多
const handleMore = () => {
loading.value = true;
// 加一个延时1s显示loading效果
setTimeout(() => {
featchList();
}, 1000);
};
const handleScrollTable = (el, binding) => {
const { infiniteScrollDisable, cb } = binding.value;
// 如果el不存在,则禁止后面IntersectionObserver的实例化
if (!el && !cb) {
return;
}
const intersectionObserver = new IntersectionObserver((enteris, observer) => {
// console.log(enteris, observer);
const [curentEnteris] = enteris;
const { intersectionRatio } = curentEnteris;
// 不可见的时候,禁止加载
if (intersectionRatio <= 0) return;
// 设置一个可以加载更多的开关
if (infiniteScrollDisable) {
cb();
}
});
// 开始监听
intersectionObserver.observe(el);
};
// 自定义一个上拉加载的指令
const vScrollTable = {
created: (el, binding, vnode, prevVnod) => {
handleScrollTable(el, binding);
},
};
</script>
<style>
.load-more-btn {
display: flex;
align-items: center;
justify-content: center;
}
</style>
打开页面,我们可以看到
点击加载操作就会加载更多,当滚动到底部时,就会加载更多。当数据加载完时,我们就设置hasMore = false;
核心代码非常简单,就是利用IntersectionObserver监测目标元素的可见,当目标元素可见时,我们加载更多,在目标元素不可见时,我们禁止加载更多,当数据加载完了,就禁止加载更多。
总结
1.使用vite与vue3模板搭建一个简易的demo模板,结合vue-router、mockjs、elementPlus,fetch实现基本路由搭建,数据请求
2.了解核心IntersectionObserverAPI,用vue3指令,实现加载更多,这里用指令的原因是因为可以在多个类似模块复用指令内部那段逻辑,这样可以提高我们业务功能的复用能力
3.我们看到在vue3中script中使用了setup,在注册组件和模板上使用的变量,当前组件可以直接使用。如果你未在script中使用setup,那么就要与组合式API一样使用setup,返回模板中使用的变量以及绑定的方法,并且注册局部组件依旧要像以前方式一样在component中引入
4.更多关于IntersectionObserver的实践,我们可以用它做图片懒加载,视频播放暂停与播放等,具体可以参考这篇文章IntersectionObserver[2]
5.本文示例源码地址intersectionObserver[3]
参考资料
[1]vite: https://vitejs.cn/guide/#trying-vite-online
[2]IntersectionObserver: https://wangdoc.com/webapi/intersectionObserver.html
[3]intersectionObserver: https://github.com/maicFir/lessonNote/tree/master/javascript/04-IntersectionObserver/intersectionObserver
边栏推荐
- 数据库系统原理与应用教程(059)—— MySQL 练习题:操作题 1-10(三)
- R语言ggplot2可视化:使用ggpubr包的ggviolin函数可视化小提琴图、设置draw_quantiles参数添加指定分位数横线(例如,50%分位数、中位数)
- es6你用过哪些惊艳的写法
- Some thoughts on.Net desktop development
- Can second uncle cure young people's spiritual internal friction?
- Humiliation, resistance, reversal, 30 years, China should win Microsoft once
- R language uses dpois function to generate Poisson distribution density data and plot function to visualize Poisson distribution density data
- 产品经理:岗位职责表
- Holes in [apue] files
- Force buckle 2354. Number of high-quality pairs
猜你喜欢

在 Kubernetes 中部署应用交付服务(第 1 部分)

Product Manager: job responsibility table

产品经理:岗位职责表

word打字时后面的字会消失是什么原因?如何解决?
![[security] read rfc6749 and understand the authorization code mode under oauth2.0](/img/dc/e6d8626195b2e09a6c06050a9b552e.jpg)
[security] read rfc6749 and understand the authorization code mode under oauth2.0

Use non recursive method to realize layer traversal, preorder traversal, middle order traversal and post order traversal in binary tree

No swagger, what do I use?

30 day question brushing training (I)

倒计时 2 天!2022 中国算力大会:移动云邀您共见算力网络,创新发展

Better and more modern terminal tools than xshell!
随机推荐
Use non recursive method to realize layer traversal, preorder traversal, middle order traversal and post order traversal in binary tree
Jenkins -- continuous integration server
记一次使用pdfbox解析pdf,获取pdf的关键数据的工具使用
Tutorial on the principle and application of database system (062) -- MySQL exercise questions: operation questions 32-38 (6)
解决跨越的几种方案
【安全】 阅读 RFC6749 及理解 Oauth2.0 下的授权码模式
vite在项目中配置路径别名
Can second uncle cure young people's spiritual internal friction?
Tutorial on the principle and application of database system (058) -- MySQL exercise (2): single choice question
[dark horse morning post] byte valuation has shrunk to $270billion; "Second uncle" video author responded to plagiarism; Renzeping said that the abolition of the pre-sale system of commercial housing
使用 IPtables 进行 DDoS 保护
R语言ggplot2可视化:可视化散点图并为散点图中的数据点添加文本标签、使用ggrepel包的geom_text_repel函数避免数据点标签互相重叠(自定义指定字体类型font family)
7. Dependency injection
安全保障基于软件全生命周期-NetworkPolicy应用
Half wave rectification light LED
30 day question brushing training (I)
R language ggplot2 visualization: visualize the scatter diagram and add text labels to the data points in the scatter diagram, using geom of ggrep package_ text_ The rep function avoids overlapping da
算法---不同路径(Kotlin)
SAP UI5 FileUploader 控件实现本地文件上传,接收服务器端的响应时遇到跨域访问错误的试读版
数据库系统原理与应用教程(061)—— MySQL 练习题:操作题 21-31(五)