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 相关文章推荐
javascript开发随笔一 preventDefault的必要
Nov 25 Javascript
js面向对象 多种创建对象方法小结
May 21 Javascript
json原理分析及实例介绍
Nov 29 Javascript
jquery ui resize 中border-box的bug修正
Apr 26 Javascript
JavaScript基础知识点归纳(推荐)
Jul 09 Javascript
jQuery实现定位滚动条位置
Aug 05 Javascript
第一次接触神奇的Bootstrap导航条
Aug 09 Javascript
移动设备手势事件库Touch.js使用详解
Aug 18 Javascript
使用async await 封装 axios的方法
Jul 09 Javascript
详解vue中$nextTick和$forceUpdate的用法
Dec 11 Javascript
javascript实现留言板功能
Feb 08 Javascript
使用jQuery实现购物车
Oct 29 jQuery
浅谈关于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
php分页思路以及在ZF中的使用
2012/05/30 PHP
PHP 处理TXT文件(打开/关闭/检查/读取)
2013/05/13 PHP
解析crontab php自动运行的方法
2013/06/24 PHP
Yii框架获取当前controlle和action对应id的方法
2014/12/03 PHP
PHP精确计算功能示例
2016/11/29 PHP
JavaScript constructor和instanceof,JSOO中的一对欢喜冤家
2009/05/25 Javascript
html超链接打开窗口大小的方法
2013/03/05 Javascript
Javascript之this关键字深入解析
2013/11/12 Javascript
js实现圆盘记速表
2015/08/03 Javascript
js实现仿百度风云榜可重复多次调用的TAB切换选项卡效果
2015/08/31 Javascript
ashx文件获取$.ajax()方法发送的数据
2016/05/26 Javascript
详解jQuery中的事件
2016/12/14 Javascript
javascript和jQuery中的AJAX技术详解【包含AJAX各种跨域技术】
2016/12/15 Javascript
简单好用的nodejs 爬虫框架分享
2017/03/26 NodeJs
如何编写jquery插件
2017/03/29 jQuery
设置cookie指定时间失效(实例代码)
2017/05/28 Javascript
详解vue2.0 资源文件assets和static的区别
2018/11/27 Javascript
uni app仿微信顶部导航条功能
2019/09/17 Javascript
[02:02]2018DOTA2亚洲邀请赛Mineski赛前采访
2018/04/04 DOTA
python实现同时给多个变量赋值的方法
2015/04/30 Python
浅谈tensorflow1.0 池化层(pooling)和全连接层(dense)
2018/04/27 Python
python计算两个地址之间的距离方法
2018/06/09 Python
Python3.x+pyqtgraph实现数据可视化教程
2020/03/14 Python
pycharm实现print输出保存到txt文件
2020/06/01 Python
Scrapy框架介绍之Puppeteer渲染的使用
2020/06/19 Python
AmazeUI框架搭建的方法步骤(图文)
2020/08/17 HTML / CSS
Vichy薇姿加拿大官网:法国药妆,全球专业敏感肌护肤领先品牌
2018/07/11 全球购物
linux面试题参考答案(11)
2012/05/01 面试题
生物学学生自我评价
2014/01/17 职场文书
电工工作职责范本
2014/02/22 职场文书
劲霸男装广告词
2014/03/21 职场文书
房屋租赁合同补充协议
2014/10/11 职场文书
小学英语听课心得体会
2016/01/14 职场文书
2016年万圣节家长开放日活动总结
2016/04/05 职场文书
Html分层的box-shadow效果的示例代码
2021/03/30 HTML / CSS
MySql存储过程之逻辑判断和条件控制
2021/05/26 MySQL