background
The project needed to render a Tree component of 5000+ nodes, but after the introduction of element Tree component, the performance was found to be very poor, whether scrolling, expanding/unwinding nodes or clicking nodes were very obvious, and the problem was found by running performance data
12s
10s
createChildren
Optimization idea
Can be seen from the above analysis of performance problems because rendering nodes lead to too much, so to solve this problem is to minimize the render of the node, however, in the industry and the similar solution is the core concept of the virtual virtual list according to scroll to control rendering a list of the visible area In this way, You can dramatically reduce node rendering and improve performance
The specific steps are as follows:
- Flatten the recursive tree data, but keep references to parent and child (on the one hand, to find references to child and parent nodes, and on the other hand, to calculate the list of visible areas)
- Dynamically calculate the height of the scroll area (many components of the virtual long list are fixed height, but because this is a tree, the node needs to be collapsed/expanded, so the height is dynamically calculated)
- Render the corresponding nodes according to the height visible and how far they are rolled
Code implementation
Minimal code implementation
<template>
<div class="b-tree" @scroll="handleScroll">
<div class="b-tree__phantom" :style="{ height: contentHeight }"></div>
<div
class="b-tree__content"
:style="{ transform: `translateY(${offset}px)` }"
>
<div
v-for="(item, index) in visibleData"
:key="item.id"
class="b-tree__list-view"
:style="{ paddingLeft: 18 * (item.level - 1) + 'px' }"
>
<i :class="item.expand ? 'b-tree__expand' : 'b-tree__close' " v-if="item.children && item.children.length" />
<slot :item="item" :index="index"></slot>
</div>
</div>
</div>
</template>
<style>
.b-tree {
position: relative;
height: 500px;
overflow-y: scroll;
}
.b-tree__phantom {
position: absolute;
left: 0;
top: 0;
right: 0;
z-index: -1;
}
.b-tree__content {
position: absolute;
left: 0;
right: 0;
top: 0;
min-height: 100px;
}
.b-tree__list-view{
display: flex;
align-items: center;
cursor: pointer;
}
.b-tree__content__item {
padding: 5px;
box-sizing: border-box;
display: flex;
justify-content: space-between;
position: relative;
align-items: center;
cursor: pointer;
}
.b-tree__content__item:hover..b-tree__content__item__selected {
background-color: #d7d7d7;
}
.b-tree__content__item__icon {
position: absolute;
left: 0;
color: #c0c4cc;
z-index: 10;
}
.b-tree__close{
display:inline-block;
width:0;
height:0;
overflow:hidden;
font-size:0;
margin-right: 5px;
border-width:5px;
border-color:transparent transparent transparent #C0C4CC;
border-style:dashed dashed dashed solid
}
.b-tree__expand{
display:inline-block;
width:0;
height:0;
overflow:hidden;
font-size:0;
margin-right: 5px;
border-width:5px;
border-color:#C0C4CC transparent transparent transparent;
border-style:solid dashed dashed dashed
}
</style>
<script>
export default {
name: "bigTree".props: {
tree: {
type: Array.required: true.default: []},defaultExpand: {
type: Boolean.required: false.default: false
},
option: {
// Configure objects
type: Object.required: true.default: {}
}
},
data() {
return {
offset: 0.// translateY offset
visibleData: []
};
},
computed: {
contentHeight() {
return((this.flattenTree || []).filter(item= > item.visible).length *
this.option.itemHeight +
"px"
);
},
flattenTree() {
const flatten = function(
list,
childKey = "children",
level = 1,
parent = null,
defaultExpand = true
) {
let arr = [];
list.forEach(item= > {
item.level = level;
if (item.expand === undefined) {
item.expand = defaultExpand;
}
if (item.visible === undefined) {
item.visible = true;
}
if(! parent.visible || ! parent.expand) { item.visible =false;
}
item.parent = parent;
arr.push(item);
if(item[childKey]) { arr.push( ... flatten( item[childKey], childKey, level +1, item, defaultExpand ) ); }});return arr;
};
return flatten(this.tree, "children".1, {
level: 0.visible: true.expand: true.children: this.tree
});
}
},
mounted() {
this.updateVisibleData();
},
methods: {
handleScroll(e) {
const scrollTop = e.target.scrollTop
this.updateVisibleData(scrollTop)
},
updateVisibleData(scrollTop = 0) {
const start = Math.floor(scrollTop / this.option.itemHeight);
const end = start + this.option.visibleCount;
const allVisibleData = (this.flattenTree || []).filter(
item= > item.visible
);
this.visibleData = allVisibleData.slice(start, end);
this.offset = start * this.option.itemHeight; }}};</script>
Copy the code
Here are the details:
- The entire container uses relative positioning to avoid page backflow during scrolling
- The phantom container makes the scroll bar appear in order to spread the height
- FlattenTree adds level, expand, and Visibel attributes to flat tree data of recursive structure, representing node level, expansion, and visibility respectively
- ContentHeight dynamically calculates the height of containers; hidden (stowed) nodes should not be counted as part of the total height
This gives you a basic prototype of the tree component for rendering big data. How does node expansion/collapse work
Node expansion and collapse
References to children are kept in flattenTree, and only need to be shown/hidden by expanding/collapsing them
{
methods: {
// Expand the node
expand(item) {
item.expand = true;
this.recursionVisible(item.children, true);
},
// Collapse the node
collapse(item) {
item.expand = false;
this.recursionVisible(item.children, false);
},
// recursive node
recursionVisible(children, status) {
children.forEach(node= > {
node.visible = status;
if (node.children) {
this.recursionVisible(node.children, status); }}}})Copy the code
conclusion
Compare some performance data before and after optimization
Element tree component
First render (all folded up)
scripting:
rendering:
scripting:
rendering:
Optimized tree component
First render (full unfold)
scripting:
6.8 times
rendering:
65 times
Node expansion
scripting:
rendering:
113 times
The big tree components
Finally encapsulated into vuE-big-tree component for call, welcome star~~~