翻译
IntersectionObserver:交叉口监控
异步操作,不随着目标元素的滚动同步触发。
即只有线程空闲下来,才会执行观察器。这意味着,这个观察器的优先级非常低,只在其他任务执行完,浏览器有了空闲才会执行。
作用
监听目标元素与其祖先或视窗交叉状态的手段,其实就是观察一个元素是否在视窗可见。
语法
创建原生构造函数 IntersectionObserver的一个实例,接受两个参数两个参数:callback是当元素可见性变化时的回调函数,option是配置对象(该参数可选)。
let io= new IntersectionObserver(callback, options)
实例的方法
构造函数的返回值是一个观察器实例。
特别注意:实例io.observe
方法可以指定观察哪个元素。
// 开始观察,
io.observe(document.getElementById('example'));
// 停止观察
io.unobserve(element);
// 关闭观察器
io.disconnect();
若想要观察多个元素,可以调用多次io.observe(document.getElementById('***'));
io.observe(document.getElementById('example1'));
io.observe(document.getElementById('example2'));
io.observe(document.getElementById('example3'));
参数
callback:当目标元素有可见性变化的时候,执行
callback默认会触发两次。一次是目标元素刚刚进入视口(开始可见),另一次是完全离开视口(开始不可见)。
var io = new IntersectionObserver(
entries => {
//entries:数组
console.log('example',entries);
}
);
entries
是个数组,如果有受观察的多个元素的可见性发生了变化,entries 内的数组成员就能有多个。
entries分析
console.log('example',entries);
- boundingClientRect:目标元素与可视视图的距离,内容与原生的document.boundingClientRect()返回值一致。其中一共有六个属性:left、right、top、bottom、x、y、width、height。
- intersectionRatio:目标元素的可见比例,即intersectionRect占boundingClientRect的比例,完全可见时为1,完全不可见时小于等于0。
- intersectionRect:目标元素与视口(或根元素)的距离,也有有六个属性:left、right、top、bottom、x、y、width、height。
- isIntersecting:目标元素与视图是否相交。
- isVisible:目标元素是否可见。
- rootBounds:根元素的矩形区域的信息,getBoundingClientRect()方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回null
- target:目标元素。
- time:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒。
option:配置,可以控制什么时候触发callback回调
var io = new IntersectionObserver(
//callback
entries => {},
//option
{
root: document.querySelector('.container'),
rootMargin: "500px 0px"
threshold: [0, 0.25, 0.5, 0.75, 1],
}
);
threshold: [0, 0.25, 0.5, 0.75, 1]
表示,目标元素 0%、25%、50%、75%、100% 可见时,会触发回调函数。- root:更改目标元素有可见性变化的参照物。默认是目标元素与根节点之间的比较,现在为
class=‘container’
与目标元素的对比。当.example
出现在.container
视图之中,才会触发callback
回调。 - rootMargin: “500px 0px” ,不懂这是干嘛的,还没用到。
vue中基本使用方法
<template>
<div class="box">
<div class="container" id="container">
<div class="example" id="example1"></div>
</div>
</div>
</template>
<script>
export default {
mounted () {
this.observer()
},
methods: {
observer(){
let io = new IntersectionObserver(
entries => {
console.log('example',entries);
},
{
root:document.getElementById('container'),
threshold: [0, 0.25, 0.5, 0.75, 1]
}
)
io.observe(document.getElementById('example1'));
}
}
}
</script>
<style lang="less" scoped>
.box{
height: 100vh;
overflow: auto;
background-color: darkgray;
.container{
width: 500px;
height: 500px;
overflow: auto;
margin: 0 auto;
background-color: antiquewhite;
margin-top: 100px;
.example{
width: 200px;height: 200px;
margin-top: 500px;
background-color: brown;
margin-bottom: 100px;
margin-left: 100px;
}
}
}
</style>
懒加载图片正文
思路
-
图片懒加载的图片一般都是一个list,那么一个list就需要用v-for循环去渲染展示。在html的
<img>
便签中,用自定义的一个data-src
代替src
,为了后期加载的时候能够通过dom元素获取到在src上对应的图片url。<div class="img-item" v-for="(item,index) in imageList" :key="index" > <img class="my-img" :data-src='item.PicUrlComplete' > </div>
-
获取dom元素需要在图片标签渲染结束之后,这里就涉及到vue的生命周期问题。实际操作中可能会因为在没有渲染完成之前就获取不到dom元素这样的问题。这里我是在获取接口,返回数据成功之后在
this.$nextTick
获取目标元素my-img
。this.$nextTick(function(){ this.imageDoms = Array.from(document.querySelectorAll(".my-img")); })
原本这里是想使用静态数据,但是为了更加贴合实际,还是选择使用获取接口的方式,这样会发现很多问题。
-
创建
IntersectionObserver
的实例之后,由于目标元素有多个,所以就要一个个去设置监听。let io = new IntersectionObserver( entries => { entries.forEach(item=>{ if(item.isIntersecting){ item.target.src = item.target.dataset.src; } }) }, ) //循环设置监听 this.imageDoms.forEach(function(item) { io.observe(item); });
-
展示:在
IntersectionObserver
实例的 回调中设置,目标元素有多个,那么回调值也循环去处理。一旦目标元素出现(item.isIntersecting>=0
),那么就将目标元素的item.target.dataset.src
赋值给item.target.src
,这样对于的图片上就有src值,就能展示图片了。 -
最后记得要在
beforeDestroy生命周期
里面将创建的IntersectionObserver
实例销毁,不然会造成一定的内存泄漏。
完整代码
<template>
<div class="box">
<div class="img-list">
<div class="img-item" v-for="(item,index) in imageList" :key="index" >
<img class="my-img" :data-src='item.PicUrlComplete' >
</div>
</div>
</div>
</template>
<script>
import {
infoposterlist,
} from "@/api/wyUsedInterface.js"
export default {
data() {
return {
imageList: [],
io:null
}
},
mounted () {
this.getsterPic()
},
beforeDestroy() {
this.io.disconnect()
},
methods: {
observer(){
let imageDoms = Array.from(document.querySelectorAll(".my-img"));
this.io = new IntersectionObserver(
entries => {
entries.forEach(item=>{
if(item.isIntersecting){
item.target.src = item.target.dataset.src;
}
})
},
)
imageDoms.forEach((item)=> {
this.io.observe(item);
});
},
async getsterPic(){
try{
let data = {
***
}
let res = await infoposterlist(data)
if(res.IsSuccess){
this.imageList = res.Result.Results
this.$nextTick(function(){
this.observer()
})
}
}catch(e){
}finally{
}
},
}
}
</script>
<style lang="less" scoped>
.box{
height: 100vh;
overflow: auto;
background-color: darkgray;
.img-list{
margin-top: 1000px;
position: relative;
width:100%;
.img-item{
float: left;
width: 200px;
padding: 5px;
img{
border: 1px solid #ddd;
width: 100%;
object-fit: contain;
}
}
}
}
</style>