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