常见大数据使用场景:
一、视频满屏的弹幕和右侧同步展示实时列表
如何在实时展示数据的同时不让页面卡顿
二、如果服务端返回成千上万条数据
如何优雅的展示,不让页面卡顿
极端测试:在vue项目中一次加载比如2000条数据,会有警告:Error in v-on handler: “Error: Rendered items limit reached 如下图:
问题原因
- 当js操作dom时:对dom的操作都会触发”重排” (包含重绘或回流),这严重影响到能耗。所以我们要尽可能的减少dom操作来减少”重排”
- 当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元素
什么是文档碎片
- 文档碎片是一个容器,用于暂时存放创建的dom元素。因为文档片段存在于内存中,并不在DOM树中,所以将子元素插入到文档片段时不会引起页面回流(reflow)
- 当请求把一个 DocumentFragment 节点插入文档树时,插入的不是 DocumentFragment 自身,而是它的所有子孙节点。这使得 DocumentFragment 成了有用的占位符,暂时存放那些一次插入文档的节点。它还有利于实现文档的剪切、复制和粘贴操作
- 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并没有因为数据的过于庞大而变得非常多,解决了页面卡死的问题
常用框架的虚拟列表包
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
说明:
- react-virtualized 是一个功能非常强大的库,其提供了
Grid
、List
、Table
、Collection
以及Masonry
等 五个主要组件,覆盖了常见场景下的长列表数据渲染 - 支持列表项的动态高度和固定高度,与之相关的两个主要属性有
estimatedRowSize
和rowHeight
。rowHeight
用于设置列表项的高度:(index: number): number - 如果不知道
rowHeight
的值,则可用estimatedRowSize
属性给列表项元素一个预估的高度,这样就能依赖预估高度计算列表内容的总高度,并且总高度随着列表项的渲染而渐进调整。这个在列表项是动态高度的场景下很有用,可以初始化内容的总高度以撑开容器元素,使其可在垂直方向滚动