background

A recent company project (VUE) required 500 pieces of data to be displayed on a single page, thinking that simple rendering would do the job. I didn’t expect that the initial rendering would take too long, and updating the list during batch operations would cause the page to stall. This paper records the optimization process of this list. The main reason for this is that colleagues render too much DOM, so start with that.

The principle of

Prepare to use virtual list, virtual list is actually an implementation of on-demand display, that is, only render the visible area of the screen, do not render or partially render the data in the invisible area of the scheme, so that the long list can be rendered with high performance.

Assumptions:

There are 500 pieces of data, and each piece of data is 10px high, so the entire list is 5000px high and the viewable area of the screen is 100px high

For the first time loading

Render the first oneArticle 10.Data, other data hidden

In the rolling

Scroll the100pxBefore,Article 10.Scroll to the invisible area,Article 11-20Needs to be displayed in the visible area

pre-rendered

In order to prevent the white screen after scrolling and affect the experience, it is necessary to pre-render and reserve several renders

implementation

HTML part
  • InfiniteScrollContainerIt’s a rolling container
  • InfiniteScrollHeightOpen the container to form a scroll bar
  • InfiniteScrollDisplays a list of renders
  • InfiniteScrollItemSingle line render of scroll list
  • listHeightAdd up all the single row heights
  • translateYIs the offset of the scroll bar
<div @scroll="scrollTops" class="InfiniteScrollContainer">
    <div class="InfiniteScrollHeight" :style="{ height: listHeight + 'px' }"></div>
    <div class="InfiniteScroll" :style="{ transform: 'translate3d(0,' + (renderList[0] ? renderList[0].infiniteScrollTop : 0) + 'px,0)' }">
        <div class="InfiniteScrollItem" :data-key="item.infiniteScrollId" :ref="'InfiniteScrollItem' + item.infiniteScrollId" v-for="(item, index) in renderList" :key="item.infiniteScrollId + '_' + item.infiniteScrollKey">
            <slot :item="item" :index="index" />
        </div>
    </div>
</div>
Copy the code
Js part
export default {
    name: "Scroll".data() {
        return {
            scrollHeight: 0.// The height of the viewable area of the screen
            renderList: [].// A list of objects to render
            listAll: []./ / total list
            scrollTop: 0.// How far did you roll
            listHeight: 0.// Calculate the height of the container space
        };
    },
    // constructs the data passed in from outside the component
    props: {
        // Data list
        datas: {
            default: () = > {
                return [];
            },
            type: Array,},// Calculate the default height
        defHeight: {
            default: 50.type: Number,}},watch: {
        // The incoming data changes to get the list again
        datas(v) {
            this.init(); }},mounted() {
        this.getInfiniteScrollHeight();
        this.init();
    },
    methods: {
        // Get the height of the visible area
        getInfiniteScrollHeight() {
            if (document.querySelector(".InfiniteScrollContainer")) {
                this.scrollHeight = document.querySelector(".InfiniteScrollContainer").offsetHeight; }},init() {
            if (this.datas.length > 0) {
                this.listAll = JSON.parse(JSON.stringify(this.datas));
                let height = 0;
                this.listAll.forEach((i, index) = > {
                    i.infiniteScrollId = index; // Store the subscript of the current row
                    i.infiniteScrollKey = Date.parse(new Date()); // vue-for is the unique value used in the loop
                    i.infiniteScrollTop = index === 0 ? 0 : this.listAll[index - 1].infiniteScrollTop + this.listAll[index - 1].infiniteScrollHeight; // The current row distance from the top of the list
                    i.infiniteScrollHeight = this.defHeight; // The height of a single row
                    height += this.defHeight;
                });
                this.listHeight = height; // Total height of the list
                this.renderList = this.listAll.slice(0.30); // Get the list of initial renders}},scrollTops(e) {
            // Calculate the distance beyond the scroll
            if (Math.abs(e.target.scrollTop - this.scrollTop) > this.defHeight) {
                this.scrollTop = e.target.scrollTop;
                this.getRenderList(); }},getRenderList() {
            let start = Math.floor((this.scrollTop - 300 > 0 ? this.scrollTop - 300 : 0) / this.defHeight); // Do not prerender if the scroll area is less than 300
            let count = start + Math.ceil((this.scrollHeight + 600) / this.defHeight); // Added a 300-pixel offset grid for pre-rendering
            this.renderList = this.listAll.slice(start, Math.min(count, this.listAll.length)); // Get the list of renders
        },
        // Get data after plug-in modification
        getAllList() {
            let list = [];
            this.listAll.forEach((e) = > {
                let i = Object.assign({}, e);
                delete i.infiniteScrollId;
                delete i.infiniteScrollKey;
                delete i.infiniteScrollTop;
                delete i.infiniteScrollHeight;
                list.push(i);
            });
            returnlist; ,}}};Copy the code

Highly dynamic

Above is the implementation of the fixed height of list items, but in practice, the height of list items is often determined by the content. Therefore, the above scheme does not apply, and the above scheme needs to be extended a bit. Part of the modified code is shown

Gets a single height
init() {
    if (this.datas.length > 0) {
        this.listAll = JSON.parse(JSON.stringify(this.datas));
        / /...
        this.renderList = this.listAll.slice(0.30); // Get the list of initial renders
        this.$nextTick(() = > {
            this.renderList.forEach((i) = > {
                this.renderListHeightChange(i); }); }); }},// The list is rendered to get the true height of the list
renderListHeightChange(v) {
    if(! v.hasRenderDom) {let id = v.infiniteScrollId;
        let offsetHeight = this.$refs["InfiniteScrollItem" + id][0].offsetHeight;
        this.listAll[id].infiniteScrollHeight = offsetHeight;
        this.listAll[id].hasRenderDom = true;
        // re-count height prevention
        for (let i = 0; i < this.listAll.length; i++) {
            if (i > 0) {
                this.listAll[i].infiniteScrollTop = this.listAll[i - 1].infiniteScrollTop + this.listAll[i - 1].infiniteScrollHeight; }}}},// Whether the order is visible
itemHasShow(item) {
    if (item.infiniteScrollTop < this.scrollTop && item.infiniteScrollTop + item.infiniteScrollHeight > this.scrollTop + this.scrollHeight) {
        return true;
    } else if (item.infiniteScrollTop > this.scrollTop && item.infiniteScrollTop < this.scrollTop + this.scrollHeight) {
        return true;
    } else {
        return item.infiniteScrollTop + item.infiniteScrollHeight > this.scrollTop - 300 && item.infiniteScrollTop + item.infiniteScrollHeight < this.scrollTop + this.scrollHeight + 300; }},getRenderList() {
    for (let i = 0; i < this.listAll.length; i++) {
        if (this.itemHasShow(listAll[i])) {
            let count = i + this.showCount; // Estimate the minimum number of data prop incoming that a page + prerendered portion can display
            this.renderList = this.listAll.slice(i, Math.min(count, this.listAll.length)); // Get the list of renders
            this.$nextTick(() = > {
                this.renderList.forEach((i) = > {
                    this.renderListHeightChange(i);
                });
            });
            break}}},Copy the code
The total height of the list should be calculated in real time
computed: {
    // Calculate the height of the container space
    listHeight() {
        let height = 0;
        this.listAll.forEach((i) = > {
            height += i.infiniteScrollHeight;
        });
        returnheight; }},Copy the code

Click here to see the full code

Click to view the online DEMO