前端虚拟动态列表方案

2,792 阅读3分钟

背景

  • 需求是这样:详情页面展示大量html渲染出来的dom元素,每个子元素的高度是动态的,需同时具备目录功能;
  • 出现的问题:页面加载卡顿、数据渲染耗时长,在低配置时 even bad
  • 如何解决呢:需要性能优化哦

方法

  • 解决的方法:
  1. lazy load 懒加载
  2. 虚拟列表

对比

  1. 懒加载可行么?

大多数会想到懒加载,懒加载是随着页面滚动触发加载,但随之而来的页面DOM元素的不断增加,页面仍旧会卡哦,还卡哦遂放弃!

  1. 虚拟列表是什么?

虚拟列表,字面含义当然是虚拟的列表数据,即你看到并非列表完整数据;

  • 可视区域好比舞台,完整列表数据好比一行排队上台表演的表演者,当演出开始,表演者群体(完整列表数据)根据时间安排依次入场,在舞台上表演,而表演完成的表演者按顺序离台,就这样,随着时间推移,表演完成,而且非常有序高效。
  • 打比方如此,前端所说的虚拟列表道理类似,注意需要给定一个高度的容器,页面的DOM元素不会随着页面滚动会越来大,占用过多的浏览器资源,非常好用哦
  1. 虚拟列表如何实现?

感谢前端大佬,感谢开源,在此使用了两款虚拟列表插件

引入

import { DynamicScroller, DynamicScrollerItem } from 'vue-virtual-scroller‘

export default {
    components: {
        DynamicScroller,
        DynamicScrollerItem
    }
}

使用

<template>
    <DynamicScroller
       :items="list"
       :min-item-size="50"
       ref="dynamicScroller
    >
      <template v-slot="{ item, index, active }">
          <DynamicScrollerItem
              :item="item"
              :active="active"
          >
              //业务组件数据
              ...
          </DynamicScrollerItem>
      </template>
    </DynamicScroller>
</template>

如此可实现虚拟动态列表加载

引入

import VirtualList from 'vue-virtual-scroll-list‘

export default {
    components: {
        VirtualList
    }
}

使用

  1. List.vue 父级组件
<template>
    <VirtualList
       :data-sources="list"
       :data-key="'id'"
       :data-component="item"
       :estimate-size="50"
       ref="virtualList
    />
</template>
<script>
import Item from './Item
export default {
    data() {
        item: Item,
        list: [{id: 1,...}, ...]
    },
    mounted() {
        // 监听子组件数据返回,父子通信
        this.$on('change', (index) => {
            console.log(index)
           // 让目录的index = index 从而达到监听正文滚动 目录滚动效果
        })
    },
    methods: {
        // 点击目录定位到对应位置
        clickToIndex (index) {
            // 调用提供方法
            this.$refs.virtualList.scrollToIndex(index)
        },
    }
    
}
</script>
  1. Item.vue 子级组件
<template>
    <div v-observe-visibility="handleVisibilityChange" :data-index="index">
        // 业务数据渲染
        {{ source.id }}
    </div>
</template>
<script>
// vue监听元素显示在可视区域的插件
import { ObserveVisibility } from 'vue-observe-visibility'
export default {
    directives: {
        ObserveVisibility
    },
    props: {
        // 父级list中的每一项数据
        source: {
            type: Object,
        },
        index: {
            type: Number, // 也可以在list注入,data中定义
        }
    },
    methods: {
        // 注册通信方案
        dispatch(componentName, eventName, ...rest) {
            let parent = this.$parent || this.$root
            let name = parent.options.name
            while (parent && (!name || name !== componentName)) {
                parent = parent.$parent
                if (parent) {
                    name = parent.$option.name
                }
            }
            if (parent) {
                parent.$emit.apply(parent, [eventName].concat(rest))
            }
        },
        hanldeVisibilityChange(isVisible, entry) {
            if (isVisible) {
               // 显示在可视区内,index为绑定的index
               this.dispatch('List', 'change', index)
            }
        }
    }
}
</script>

如此可实现虚拟动态列表加载。

结合实际业务需求,可选用以上两个虚拟列表组件,当然你也可以造轮子,优秀哦

源码和原理

当完成业务需求,仍需静下心来思考虚拟动态列表的原理,分析源码,温故而知新!

注意哪些点:

  1. 要给定固定高度的容器,好比你要给一个舞台,才可以表演

  2. 分情况,每一项高度固定,如table列表数据,选用对应的组件;每一项动态高度,如本文所讲,每一块由v-html渲染,要选对哦;

  3. 滚动监听,怎么做,性能优化到极致;两种思路,监听scroll事件,无疑开销会大,性能不是最佳。 new IntersectionObserver是性能优化最优选,当然不考虑那么多的兼容问题;

先说 vue-virtual-scroller

常用两种类型

  1. table 每行高度固定哦, 选用RecyleScroller

  2. 聊天室页面 每行高度虽内容大小而不一, 选用 DynamicScroller

分析源码发现:DynamicScroller 根据 RecyleScroller 进化而来哦

在谈 vue-virtual-scroll-list

目前维护较好, 22days 前发布了最新的release

提供了较多的 props 和 event 哦! 棒!

提供examples:tangbc.github.io/vue-virtual…

贴心!

就此,总结,记录,留用,分享,少走坑,时不我待!