Vue.js 无限滚动列表性能优化方案


Posted in Javascript onDecember 02, 2019

问题

大家都知道,Web 页面修改 DOM 是开销较大的操作,相比其他操作要慢很多。这是为什么呢?因为每次 DOM 修改,浏览器往往需要重新计算元素布局,再重新渲染。也就是所谓的重排(reflow)和重绘(repaint)。尤其是在页面包含大量元素和复杂布局的情况下,性能会受到影响。那对用户有什么实际的影响呢?

一个常见的场景是大数据量的列表渲染。通常表现为可无限滚动的无序列表或者表格,当数据很多时,页面会出现明显的滚动卡顿,严重影响了用户体验。怎么解决呢?

解决方案

既然问题的根源是 DOM 元素太多,那就想办法限制元素数量。

  • 限制列表对用户可见的元素数量。我们把可见区域称为 ViewPort
  • 当列表滚动时,列表种的其他元素怎么由不可见变为可见?
  • 监听列表容器元素的滚动事件,当列表里的元素进入可视区域,则添加到DOM中
  • 问题是如果一直这么滚下去,列表会越来越大。所以需要在列表元素离开 ViewPort 的时候从DOM中移除
  • 问题又来了,由于 ViewPort 刚好是一屏的大小,滚动的时候元素还没来得及渲染,会出现一段时间的空白。解决办法就是上下增加一部分数据渲染。

Vue.js 无限滚动列表性能优化方案

无限滚动的性能优化方案基本思路就是这样。

在实际项目中,我们可能不需要自己从头实现一个无限滚动列表组件,Vue.js 就有一个现成的轮子:vue-virtual-scroller。

在项目中安装这个插件:

$ npm install -D vue-virtual-scroller

项目入口文件 main.js 引入这个插件:

import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
import Vue from "vue";
import VueVirtualScroller from "vue-virtual-scroller";

Vue.use(VueVirtualScroller);

案例一:VirtualList

我们来看一个简单的例子,用vue-virtual-scroller渲染一个包含大量数据的列表。 先用JSON-Generator 生成 5000 条数据的 JSON 对象,并保存到 data.json 文件。可以用下面的规则:

[
 '{{repeat(5000)}}',
 {
  _id: '{{objectId()}}',
  age: '{{integer(20, 40)}}',
  name: '{{firstName()}} {{surname()}}',
  company: '{{company().toUpperCase()}}'
 }
]

新建一个 VirtualList.vue 文件,引入data.json,并将它赋值给组件的items属性。然后套一个 <virtual-scroller>组件:

VirtualList.vue:

<template>
 <virtual-scroller :items="items" item-height="40" content-tag="ul">
  <template slot-scope="props">
   <li :key="props.itemKey">{{props.item.name}}</li>
  </template>
 </virtual-scroller>
</template>

<script>
import items from "./data.json";

export default {
 data: () => ({ items })
};
</script>

virtual-scroller 组件必须设置 item-height 。另外,由于我们要创建一个列表,可以设置content-tag="ul",表示内容渲染成 <ul>标签。

vue-virtual-scroller 支持使用 scoped slots,增加了内容渲染的灵活性。通过使用slot-scope="props",我们可以访问 vue-virtual-scroller 暴露的数据。

props 有一个itemKey属性,出于性能考虑,我们应该在内容部分的根元素上绑定 :key="props.itemKey"。然后我们就可以通过 props.item 拿到 JSON 里的原始数据了。

如果你要给列表设置样式,可以给 virtual-scroller 设置 class属性:

<template>
 <virtual-scroller class="virtual-list" ...></virtual-scroller>
</template>

<style>
.virtual-list ul {
 list-style: none;
}
</style>

或者也可以用scoped 样式,用 /deep/选择器:

<style scoped>
.virtual-list /deep/ ul {
 list-style: none;
}
</style>

案例二: VirtualTable

类似 VirtualList,我们再看一个表格组件VirtualTable: VirtualTable.vue:

<template>
 <virtual-scroller :items="items" item-height="40" content-tag="table">
  <template slot-scope="props">
   <tr :key="props.itemKey">
    <td>{{props.item.age}}</td>
    <td>{{props.item.name}}</td>
    <td>{{props.item.company}}</td>
   </tr>
  </template>
 </virtual-scroller>
</template>

<script>
import items from "./data.json";

export default {
 data: () => ({ items })
};
</script>

这里有个小问题,我们需要增加一个 <thead>标签,用于显示列名: Age, Name 和 Company

幸好 virtual-scroller 支持 slot,可以定制各部分内容:

<main>
 <slot name="before-container"></slot>
 <container>
  <slot name="before-content"></slot>
  <content>
   <!-- Your items here -->
  </content>
  <slot name="after-content"></slot>
 </container>
 <slot name="after-container"></slot>
</main>

这些 slot 都可以放置自定义内容。container 会被 container-tag 属性值替换,默认是div,content 被 content-tag 值替换。

这里用 before-content slot 加一个thead 就行了:

<template>
 <virtual-scroller
  :items="items"
  item-height="40"
  container-tag="table"
  content-tag="tbody"
  >
   <thead slot="before-content">
    <tr>
     <td>Age</td>
     <td>Name</td>
     <td>Company</td>
    </tr>
   </thead>
   <template slot-scope="props">
    <tr :key="props.itemKey">
     <td>{{props.item.age}}</td>
     <td>{{props.item.name}}</td>
     <td>{{props.item.company}}</td>
    </tr>
   </template>
 </virtual-scroller>
</template>

请注意,我们把content-tag="table" 改成了content-tag="tbody",因为我们设置了container-tag="table",这是为了构造table 标签的常规结构。

如果要加一个 tfoot,应该知道怎么做了吧。

总结

我们了解了无限滚动列表的性能优化原理,以及利用vue-virtual-scroller Vue 插件创建了 VirtualList 和  VirtualTable 组件。如果用它们来展示前面生成的 5000 条数据,应该可以比较流畅地渲染和滚动了。更多用法可以参考 vue-virtual-scroller 文档 。

文章涉及的源码 戳这里。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JS继承 笔记
Jul 13 Javascript
SwfUpload在IE10上不出现上传按钮的解决方法
Jun 25 Javascript
JQUERY实现左侧TIPS滑进滑出效果示例
Jun 27 Javascript
使用js判断TextBox控件值改变然后出发事件
Mar 07 Javascript
jQuery的cookie插件实现保存用户登陆信息
Apr 15 Javascript
老生常谈 关于JavaScript的类的继承
Jun 24 Javascript
巧用jQuery选择器提高写表单效率的方法
Aug 19 Javascript
node.js中 stream使用教程
Aug 28 Javascript
fullpage.js全屏滚动插件使用实例
Sep 06 Javascript
微信小程序实现下拉刷新和轮播图效果
Nov 21 Javascript
VUE解决微信签名及SPA微信invalid signature问题(完美处理)
Mar 29 Javascript
小程序开发之模态框组件封装
Apr 23 Javascript
浅谈关于vue中scss公用的解决方案
Dec 02 #Javascript
详解js location.href和window.open的几种用法和区别
Dec 02 #Javascript
js实现一款简单踩白块小游戏(曾经很火)
Dec 02 #Javascript
JavaScript 自定义html元素鼠标右键菜单功能
Dec 02 #Javascript
VUE 动态组件的应用案例分析
Dec 02 #Javascript
VUE 直接通过JS 修改html对象的值导致没有更新到数据中解决方法分析
Dec 02 #Javascript
vue 动态表单开发方法案例详解
Dec 02 #Javascript
You might like
session在PHP大型web应用中的使用
2011/06/25 PHP
php中文字符串截取方法实例总结
2014/09/30 PHP
php返回当前日期或者指定日期是周几
2015/05/21 PHP
php实现带读写分离功能的MySQL类完整实例
2016/07/28 PHP
ExtJS4 Grid改变单元格背景颜色及Column render学习
2013/02/06 Javascript
不用锚点也可以平滑滚动到页面的指定位置实现代码
2013/05/08 Javascript
javascript 手动给表增加数据的小例子
2013/07/10 Javascript
可选择和输入的下拉列表框示例
2013/11/05 Javascript
nodejs导出excel的方法
2015/06/30 NodeJs
js贪吃蛇网页版游戏特效代码分享(挑战十关)
2015/08/24 Javascript
使用Javascript监控前端相关数据的代码
2016/10/27 Javascript
JavaScript 网页中实现一个计算当年还剩多少时间的倒数计时程序
2017/01/25 Javascript
Bootstrap.css与layDate日期选择样式起冲突的解决办法
2017/04/07 Javascript
JS实现自动轮播图效果(自适应屏幕宽度+手机触屏滑动)
2017/06/19 Javascript
使用vue-aplayer插件时出现的问题的解决
2018/03/02 Javascript
使用async await 封装 axios的方法
2018/07/09 Javascript
webpack实现一个行内样式px转vw的loader示例
2018/09/13 Javascript
vue-router权限控制(简单方式)
2018/10/29 Javascript
聊聊Vue中provide/inject的应用详解
2019/11/10 Javascript
vue请求数据的三种方式
2020/03/04 Javascript
JavaScript设计模式--简单工厂模式实例分析【XHR工厂案例】
2020/05/23 Javascript
浅谈Python 的枚举 Enum
2017/06/12 Python
基于Python Numpy的数组array和矩阵matrix详解
2018/04/04 Python
python如何创建TCP服务端和客户端
2018/08/26 Python
python实现微信机器人: 登录微信、消息接收、自动回复功能
2019/04/29 Python
python requests使用socks5的例子
2019/07/25 Python
python 数据生成excel导出(xlwt,wlsxwrite)代码实例
2019/08/23 Python
基于python3实现倒叙字符串
2020/02/18 Python
Superdry极度干燥美国官网:英国制造的服装品牌
2018/11/13 全球购物
丝芙兰新加坡官网:Sephora新加坡
2018/12/04 全球购物
在C++ 程序中调用被C 编译器编译后的函数,为什么要加extern "C"
2014/08/09 面试题
促销活动总结报告
2014/04/26 职场文书
酒桌上的开场白
2015/06/01 职场文书
漫画「处刑少女的生存之道」第3卷封面公开
2022/03/21 日漫
CSS实现渐变色边框(Gradient borders)的5种方法
2022/03/25 HTML / CSS
vue实现简易音乐播放器
2022/08/14 Vue.js