IntersectionObserver应用之懒加载和布局渲染

1,266 阅读4分钟

  最近在项目中,遇到要对大量图表数据渲染的情况。所以当时想着能不能只渲染视图中可见的图表呢?然后在网上搜索了一番,查到了这个有趣的知识。

1、IntersectionObserver

  为开发者提供了一种可以异步监听目标元素与其祖先或视窗(viewport)交叉状态的手段。祖先元素与视窗(viewport)被称为根(root)。

let io = new IntersectionObserver(callback, options)
`callback` 是当元素的可见性变化时候的回调函数,`options`是一些配置项(可选)。

options 主要有以下一个配置项。

const options = {
    root: null,//表示默认为窗口
    threshold: [0, 0.5, 1],//表示当观察元素出现0%、50%、100%时候就会触发回调函数
    rootMargin: '30px 100px 20px'//
}
var io = new IntersectionObserver(callback, options)
  • root
    用于观察的根元素,默认是浏览器的视口,也可以指定具体元素,指定元素的时候用于观察的元素必须是指定元素的子元素
  • threshold
    用来指定交叉比例,决定什么时候触发回调函数,是一个数组,默认是 [0]
    threshold: [0,0.5,1],//表示当观察元素出现0%、50%、100%时候就会触发回调函数
  • rootMargin
    用来扩大或者缩小视窗的的大小,使用 css 的定义方法。
    rootMargin: '10px 130 100px 50px'
    //表示`top、right、bottom` 和 `left` 的值。

callback在元素的可见性变化时,才会触发。
  callback中有一个参数 entries。它是一个IntersectionObserverEntry对象数组。如下图所示。主要有以下属性。我们常常关注的有 isIntersecting(表示当前是否可见)和 target(被观察的目标元素)以及intersectionRatio(表示元素的可见程度,0表示刚刚可见,1表示完全可见)。

2、IntersectionObserver的实例方法

  • disconnect()
    使IntersectionObserver对象停止监听工作。
  • unobserve()
    使IntersectionObserver停止监听特定目标元素。
  • observe()
    使IntersectionObserver开始监听一个目标元素。
  • takeRecords()
    返回所有观察目标的IntersectionObserverEntry对象数组。

以下是项目中的一部分html代码,为了接下来更好的展示说明。

<grid-layout
	:layout.sync="layout"
	:row-height="rowHeight"
	:is-draggable="draggable"
	:is-resizable="resizable"
	:is-mirrored="mirrored"
	:prevent-collision="preventCollision"
	:vertical-compact="true"
	:use-css-transforms="true"
	:responsive="responsive"
	@layout-mounted="layoutMountedEvent"
	@layout-updated="layoutUpdatedEvent">
	<grid-item
		v-for="item in layout"
		:key="item.i"
		:static="item.static"
		:x="item.x"
		:y="item.y"
		:w="item.w"
		:h="item.h"
		:i="item.i"
		class="grid-item"
		:index="item.i"
		@resized="resized">
			<Chart
				:optionData="item.data"
				:index='item.i'
				:type="item.displayType"
				@gridItemAllMounted="gridItemAllMountedEvent"
			/>

	</grid-item>
</grid-layout>

接下来是在项目中用于条件渲染的核心代码。

//表示所有的Chart组件都被渲染好了,此时开始监听元素
function Observer(){
    const callback = (entries) => {
	entries.forEach(item => {
	    //index是我在目标元素上的一个自定义属性
		const index = item.target.getAttribute('index');
		if (item.isIntersecting) {
		    //将当前视图中可见的echart图进行渲染
			this.$store.commit('initChart', index)
			// this.io.unobserve(item.target); // 停止观察当前元素 避免不可见时候再次调用callback函数
		} else {
			// 清除不可见的,目的是为了不渲染不在视图中的图片
			this.$store.commit('clearInvisibleChartInstance', index)
		}
	});
    }
    this.io = new IntersectionObserver(callback);
    const gridItems = document.querySelectorAll('.grid-item');
    gridItems.forEach(item => {
    	this.io.observe(item);
    });
}

在这个项目中主要有以下几点需要注意。

  • 1、因为这个项目需要根据 grid-layout组件的宽度来均分 gridItem,所以需要等到计算出 grid-layout 时才能对它的子组件进行平均分配。所以有以下的代码。
    //gridLayout组件编译好后(Mounted()),在上级组件中触发的监听函数,此时才开始根据gridLayout组件均配gridItem的宽度。
    layoutMountedEvent (colNum) {
    	this.colNum = colNum
    	this.createLayoutData(this.colNum)
    }
  • 2、需要等到所有的图表组件都渲染好后再去对图表组件进行监听。所以有以下的代码。具体如何实现可以我的github中看详细的代码实现。
    //所有的`echart组价渲染完毕后触发`
    gridItemAllMountedEvent () {
    	this.startObserver()
    },
  • 3、在元素可见时要对该元素实时监听,以便在窗口变化时做到局部渲染(条件渲染)。具体代码如下如示。主要是用到了 isIntersecting属性。
    注意 初次渲染时 item.target是所有的目标元素。而在后面利用滚动条来操作时的 item.target就是要显示和要隐藏的元素。所以可以利用这个性质。可以用一个对象来实时保存当前可见的元素。当窗口变化时,就可以额做到真正的条件渲染了。
    entries.forEach(item => {
	    //index是我在目标元素上的一个自定义属性
		const index = item.target.getAttribute('index');
		if (item.isIntersecting) {
		    //将当前视图中可见的echart图进行渲染
			this.$store.commit('initChart', index)
			// this.io.unobserve(item.target); // 停止观察当前元素 避免不可见时候再次调用callback函数
		} else {
			// 清除不可见的,目的是为了不渲染不在视图中的图片
			this.$store.commit('clearInvisibleChartInstance', index)
		}
	});

项目地址