前端大数据列表优化

常见大数据使用场景:

一、视频满屏的弹幕和右侧同步展示实时列表

如何在实时展示数据的同时不让页面卡顿

image.png

二、如果服务端返回成千上万条数据

如何优雅的展示,不让页面卡顿

极端测试:在vue项目中一次加载比如2000条数据,会有警告:Error in v-on handler: “Error: Rendered items limit reached 如下图: image.png

问题原因

  1. 当js操作dom时:对dom的操作都会触发”重排” (包含重绘或回流),这严重影响到能耗。所以我们要尽可能的减少dom操作来减少”重排”
  2. 当DOM太多,浏览器渲染就会变慢甚至会卡顿。所以可以考虑虚拟列表,保证每次展示的DOM数少

解决方案:

js动态创建元素

/**
* 代码片段一
* 每个标签就进行一次creatElement和appendChild
* 不足:多次appendChild会造成页面重排,影响性能
*/
const node = document.createElement('div');
node.className = 'giftinfo';
let msg = data.msg;

//送礼物的用户头像
let avatar = document.createElement('img');
avatar.className = 'avatar';
avatar.setAttribute('src', msg.avatar || userDefaultImg);
node.appendChild(avatar);  

//送礼物的用户名
let name = document.createElement("span");
name.className = 'name';
name.innerText = msg.nickname || '未知';  
node.appendChild(name);

//送礼物类型
let giftimg = document.createElement('img');
giftimg.className = 'img';
giftimg.setAttribute('src', getGiftLists[msg.content].url);

node.appendChild(giftimg);
/**
* 代码片段二
* 使用字符串拼接,减少appendChild插入
* 不足:如果每个列表展示很复杂,这样拼接字符串容易出错,维护成本高,大家还有更好的方案没?
*/
const li = document.createElement("li");
li.className = user_id === item.from_uid ? 'own_chat':'' + item.type === 'gift' ? 'gift-item' : '';
let imghtml = `<img src="${item.from_useravatar || userDefaultImg}" class="avatar">`; //图片地址
let namehtml = `<span class="name">${item.from_username}</span>`; //昵称

let texthtml = ``; //内容
if (item.type === "gift") {
 let giftname = `<span>送了一个<span class="giftinfo-type">${item.content.value}</span></span>`;
 let giftimg = `<img class="img" src="${item.content.url}" :alt="${item.content}">`;
 texthtml = `<div class="giftinfo">${giftname}${giftimg}</di>`;
} else {
  texthtml = `<span v-else class="text">${item.content}</span>`; //内容
}
let infotop = `<div class="infotop">${namehtml}</div>`; //顶部div
let iteminfo = `<div class="item-info">${infotop}${texthtml}</div>`; //主内容div
li.innerHTML = `${imghtml}${iteminfo}`;

# appendChild
node.appendChild(li);

使用文档碎片添加DOM元素

什么是文档碎片

  1. 文档碎片是一个容器,用于暂时存放创建的dom元素。因为文档片段存在于内存中,并不在DOM树中,所以将子元素插入到文档片段时不会引起页面回流(reflow)
  2. 当请求把一个 DocumentFragment 节点插入文档树时,插入的不是 DocumentFragment 自身,而是它的所有子孙节点。这使得 DocumentFragment 成了有用的占位符,暂时存放那些一次插入文档的节点。它还有利于实现文档的剪切、复制和粘贴操作
  3. DocumentFragment 节点不属于文档树,继承的 parentNode 属性总是 null

常用方法:

Document.createDocumentFragment()创建新的空DocumentFragment节点 Range.extractContents()或 Range.cloneContents() 获取包含现有文档的片段的 DocumentFragment节点

文档碎片有什么用

将需要添加的大量元素  先添加到文档碎片中,再将文档碎片添加到需要插入的位置,大大 减少dom操作,提高性能(IE和火狐比较明显)

/**
* 继续上边代码片段二
* 开头添加一句
* const fragment = document.createDocumentFragment();
* node.appendChild(li)改成fragment.appendChild(li)
* 最后再添加一句 node.appendChild(ragment);
*/
const fragment = document.createDocumentFragment();
#...上边代码片段二...#

#node.appendChild(li);
fragment.appendChild(li); // 先塞进文档碎片
node.appendChild(ragment); 最后将文档碎片塞入node中

说明:上述代码只是正对创建一个组DOM,如何有好多条数据,如何处理,看下边代码片段三

function getlistToHtmlStr(list = []) {
     if(this.$props.commentClose){
       //关闭聊天不进行初始化
       return
    }
     if (list.length == 0) return "";
     const total = list.length;
     const page = 0;
     this.list=[list,...this.list]
     let limit = everyAddMax; //分次渲染数据,比如每次200次
     let totalPage = 1;
     if(total > limit) {
       totalPage = Math.ceil(total / limit); //计算分几次渲染
    }else {
       limit = total;
    }
     const render = (page) => {
       if (page >= totalPage) {//分页添加数据结束
         if(this.hasMaxList) {
           this.removeList(total)
        }
         return;
      }else {
         requestAnimationFrame(() => { //使用requestAnimationFrame代替setTimeout,减少了重排的次数极大提高了性能,
           // 创建一个文档碎片:可以先把1页的li标签先放进文档碎片中,然后一次性appendChild到container中,这样减少了appendChild的次数,极大提高了性能
           const fragment = document.createDocumentFragment();
           const fornum = page * limit + limit;
           const {user_id, commentClose, needCheckInfo, from} = this;
           for (let i = page * limit; i < fornum; i++) {
             const item = list[i];
             if(item.type !== 'gift') {
             const li = document.createElement("li");
            #...此处省略代码片段二部分代码...#
             li.innerHTML = `${imghtml}${iteminfo}`;
             // 先塞进文档碎片
             fragment.appendChild(li);
          }
           // 一次性appendChild
           this.scrollBody.appendChild(fragment);
           // }
           // 新一轮添加
           render(page + 1);
         
        }, 0);
        }
    };
     render(page);
  }

虚拟列表

虚拟列表的原理

只渲染了可视区的数据,非可视区的div全部被销毁。随着页面的滚动,对应放到会不停的渲染可视区的dom,非可视区的dom被销毁。所以整个页面的DOM并没有因为数据的过于庞大而变得非常多,解决了页面卡死的问题

image.png
image.png
2019122154321754.gif

常用框架的虚拟列表包

1、Vue虚拟滚动插件:vue-virtual-scroller 官方提供Demo:https://akryum.github.io/vue-virtual-scroller/#/

参考:https://www.jb51.net/article/175580.htm

2、React虚拟滚动插件:react-virtualized 官方提供Demo:https://bvaughn.github.io/react-virtualized/#/components/Grid

说明:

  1. react-virtualized 是一个功能非常强大的库,其提供了 Grid 、 List 、 Table 、 Collection 以及 Masonry 等 五个主要组件,覆盖了常见场景下的长列表数据渲染
  2. 支持列表项的动态高度和固定高度,与之相关的两个主要属性有 estimatedRowSize 和 rowHeight 。 rowHeight 用于设置列表项的高度:(index: number): number
  3. 如果不知道 rowHeight 的值,则可用 estimatedRowSize 属性给列表项元素一个预估的高度,这样就能依赖预估高度计算列表内容的总高度,并且总高度随着列表项的渲染而渐进调整。这个在列表项是动态高度的场景下很有用,可以初始化内容的总高度以撑开容器元素,使其可在垂直方向滚动

参考

参考:https://mp.weixin.qq.com/s/a9Cso_nXjLeacilF1rs4zA

参考:https://juejin.cn/post/6844903982742110216

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇