vue針對滾動元素內部大量元素,但只有部分元素可見,對dom懶渲染,節省內存的優化

我們開發過程中經常會遇到這樣的需求,一個數據量很大的列表。
遇到,如果你有一個列表,
我們以百度信息流為例子


image.png

頁面打開,加載第一頁的數據,每次往下滾屏到接近底部,會加載下一屏,這樣也是保證獲取數據的http請求量是按需加載的。圖片懶加載,也能節省流量的資源。但是,隨著我們一直加載,產生的dom會越來越多。內存占用也會越來越高


image.png

還有我們常見的列表,很多情況下我們通過分頁來加載,但是有些情況下,比如不能使用分頁,這時候如果有千上萬的數據,就會產生大量的虛擬dom和真實dom,大量占用內存


c86bf1c1-c4e1-4103-a1e4-bc15c3d5ef61.gif

如果說上面的列表我們還能通過分頁解決,那么Select選擇框,如果有成千上萬的選項,即使我們使用了滾動加載,滾動到最后也還是會產生大量的dom,我本地測試了1萬條選項的下拉框,卡頓顯現非常明顯,點擊后需要2s才能展開選項,并且我的筆記本風扇已經開始呼呼排風了。


image.png

image.png

這三類問題都有一個相似點,大量的并列的dom,但是這些dom都在一個可滾動的容器中,無論怎么滾動,只有一部分是可見的,


未標題-1.jpg

所以如果只有可見的數據才去創建虛擬dom,就會大量節省內存,并加快渲染速度。

我們以列表來作為例子,展示解決方案


image.png
<template>
    <div class="list" style="height: 300px;overflow: scroll">
        <div v-for="(item,index) in list" :key="index" class="item">
            <div v-html="item.id"></div>
            <div v-html="item.name"></div>
            <div v-html="item.createTime"></div>
            <div>
                <Button>修改</Button>
                <Button>刪除</Button>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                list: [
                    {
                        id: 1,
                        name: 'name' + 1,
                        createTime: '2018-09-09'
                    }, {
                        id: 2,
                        name: 'name' + 2,
                        createTime: '2018-09-09'
                    }, {
                        id: 3,
                        name: 'name' + 3,
                        createTime: '2018-09-09'
                    }, {
                        id: 3,
                        name: 'name' + 3,
                        createTime: '2018-09-09'
                    }
                ]
            }
        }
    }
</script>

我們做一點改動,通過代碼生成一個10000條的list

<template>
    <div class="list" style="height: 300px;overflow: scroll">
        <div v-for="(item,index) in list" :key="index" class="item">
            <div v-html="item.id"></div>
            <div v-html="item.name"></div>
            <div v-html="item.createTime"></div>
            <div>
                <Button>修改</Button>
                <Button>刪除</Button>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        created() {
            for (let i = 0; i < 10000; i++) {
                this.list.push({
                    id: 1,
                    name: 'name' + i,
                    createTime: '2018-09-09'
                })
            }
        },
        data() {
            return {
                list: []
            }
        }
    }
</script>

根據這個想法設計的解決方案,思路有幾個關鍵點。
1、假滾動事件:拖動滾動滑塊,并不會影響左側的真實滾動,只是記錄滾動的位置。
2、根據滾動的位置,計算出對應的數據區間段,比如應該取340-350這10條。
3、根據滾動位置,和數據區間,把這幾條數據控制絕對定位,來模擬滾動效果。

第一步:假滾動事件

<template>
    <div class="list" style="height: 300px;overflow: scroll">
        <div :style="{height:list.length*40+'px'}"></div>
    </div>
</template>

我們的每一行高度是40,那么我們創建一個高度時list.length*40的空元素,那么這個空元素的高度,跟展示所有的list的高度一樣,也就是說右側的滾動條效果跟原來的效果是一樣的。


image.png

第二步,根據滾動的位置,計算出對應的數據區間段

我們需要計算開始index和長度,然后截取list數據的一部分

<template>
    <div class="list" style="height: 300px;overflow: scroll" ref="scrollDom" @scroll="scroll">
        <div :style="{height:list.length*40+'px'}"></div>
    </div>
</template>

<script>
    export default {
        created() {
            for (let i = 0; i < 10000; i++) {
                this.list.push({
                    id: 1,
                    name: 'name' + i,
                    createTime: '2018-09-09'
                })
            }
        },
        computed: {
            limitCount() {
                return 300 / 40;
            }
        },
        data() {
            return {
                startIndex:0,
                list: []
            }
        },
        methods: {
            scroll() {
                // 根據滾動的距離,估算出這個滾動位置對應的數組序列,例如滾動100px,每條40px,對應第3條
                let scrollTop = this.$refs.scrollDom.scrollTop;
                this.startIndex = Math.floor(scrollTop / 40);
                console.log(this.startIndex);
            }
        }
    }
</script>
de5555a3-508a-40ca-bef2-199b58d322df.gif

我們根據開始和結束需要,生成一個splitData,然后再把新生成的dom通過絕對位置調整。


image.png

完成的效果


4fd819ec-9f0b-49e3-b904-51cd9ac10688.gif

我們可以看到,無論怎么滾動,效果跟全部加載是一樣的,但是通過右側的控制臺發現,dom數量只有固定的這幾個,但是每個dom內部的值在不停的修改。

完整的代碼

<template>
    <div class="list" style="height: 300px;overflow: scroll" ref="scrollDom" @scroll="scroll">
        <div :style="{height:list.length*40+'px'}"></div>
        <div style="position:absolute;width: 100%" :style="{top:startIndex*40+'px'}">
            <div v-for="(item,index) in splitData" :key="index" class="item">
                <div v-html="item.id"></div>
                <div v-html="item.name"></div>
                <div v-html="item.createTime"></div>
                <div>
                    <Button>修改</Button>
                    <Button>刪除</Button>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        created() {
            for (let i = 0; i < 10000; i++) {
                this.list.push({
                    id: 1,
                    name: 'name' + i,
                    createTime: '2018-09-09'
                })
            }
        },
        computed: {
            limitCount() {
                return 300 / 40 + 2;
            },
            splitData() {
                return this.list.slice(this.startIndex, this.startIndex + this.limitCount)
            }
        },
        data() {
            return {
                startIndex: 0,
                list: []
            }
        },
        methods: {
            scroll() {
                // 根據滾動的距離,估算出這個滾動位置對應的數組序列,例如滾動100px,每條40px,對應第3條
                let scrollTop = this.$refs.scrollDom.scrollTop;
                this.startIndex = Math.floor(scrollTop / 40);
            }
        }
    }
</script>
<style scoped lang="less">
    .list {
        border: solid 1px #5f5f5f;
        background-color: white;
        margin: 100px 0;
        padding: 5px;
        width: 500px;
        position: relative;

        .item {
            display: flex;
            height: 40px;

            > * {
                flex-grow: 1;
                border: solid 1px #9e9e9e;
                padding: 3px;
            }
        }
    }
</style>
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。