虚拟列表 和一些优化思路

虚拟列表的核心思路:

  • 只渲染可视区内部的数据
  • 通过transform 中和 滚动距离
  • 通过节流函数节省性能
  • 事件代理在这里也得用一下,减少绑定事件

父组件:定义这部分没什么思路,就是定义一个容器

    <template>
      <div id="app">
        <div class="VirtualList" :style="{height: windowH +'px', width:windowW+'px'}">
          <VirtualList :listData="data" :itemSize="60"/>
        </div>
      </div>
    </template>

    <script>
        import VirtualList from "./components/VirtualList.vue";
        let d = [];
        for (let i = 0; i < 10000; i++) {
            d.push({ id: i, value: i });
        }
        export default {
            name: 'app',
            data() {
                return {
                    data: d

                };
            },
            computed:{
                windowW(){
                    return document.documentElement.clientWidth
                },
                windowH(){
                    return document.documentElement.clientHeight
                }
            },
            components: {
                VirtualList
            }
        };
    </script>

    <style>
      .VirtualList{
          display: flex;
          justify-content: center;
          width: 80%;
          height: 90%;
      }
      #app {

          height: 100%;
          width: 100%;
      }
      *{
          margin: 0;
          padding: 0;
      }
    </style>

子组件

除了上述四个思路,这里还有两个特殊点

  • 1:上面覆盖一层,用来处理滚动条
  • 2:通过visibility: hidden; 的点击穿透,绑定上点击时间。
    <template>
        <div id="virtualListContainer"
             @scroll="scrollHandler($event)"
             :style="{height:`${winHeight}px`}"
             ref="virtualListContainer"
        >
            <div
                    class="cover"
                    :style="{height: totalHeight + 'px' }"
            ></div>
            <div
                    id="virtualList"
                    :style="{transform: `translateY(${transformY}px)`}"
                    @click="doSomething($event)"
            >
                <div
                        class="virualItem"
                        v-for="(item, idx) in visiableList"
                        :data-item='item.value'
                        :key="item + idx" :style="{height: `${itemSize}px`}"
                >{{item.value}}</div>
            </div>

        </div>
    </template>
    <script>
        let lastTime = null
        let timeout
        export default {
            name:'VirtualList',
            props: {
                //所有列表数据
                listData:{
                    type:Array,
                    default:()=>[]
                },
                //每项高度
                itemSize: {
                    type: Number,
                    default:200
                }
            },
            computed:{
                totalHeight () {
                    return this.itemSize * this.listData.length
                },
                winHeight () {
                    return document.documentElement.clientHeight
                },
                pageCount () {
                    return ~~(this.winHeight / this.itemSize)
                }
            },
            mounted() {
                this.initScroll()
            },
            data() {
                return {
                    transformY: 0,
                    startIdx: 0,
                    passedIdx: 0,
                    endIdx: 0,
                    visiableList: []
                };
            },
            methods: {
                initScroll () {
                    let scrollTop = this.$refs.virtualListContainer.scrollTop
                    this.startIdx = Math.floor(scrollTop / this.itemSize)
                    this.endIdx = this.startIdx + this.pageCount
                    this.visiableList = this.listData.slice(this.startIdx, Math.min(this.endIdx, this.listData.length))
                    this.transformY = scrollTop - (scrollTop % this.itemSize)
                },
                scrollHandler () {
                    let _this = this
                    this.throttle(_this.initScroll, 100)
                },
                throttle (func, wait) {
                    let context = this
                    let now = new Date()
                    // 时间差是否大于间断时间
                    if (now - lastTime - wait > 0) {
                        if (timeout) {
                            clearTimeout(timeout)
                            timeout = null
                        }
                        func.apply(context, arguments)
                        lastTime = now

                        // 重置timeout 更新,做最后延迟处理
                    } else if (!timeout) {
                        timeout = setTimeout(() => {
                            func.apply(context, arguments)
                        }, wait)
                    }
                },
                doSomething (e) {
                    alert(`click${e.target.dataset.item}`)
                }
            }
        };
    </script>


    <style scoped>
        #virtualListContainer{
            overflow: scroll;
            position: relative;
        }
        #virtualList{

            left: 0;
            right: 0;
            top: 0;
            text-align: center;
        }
        .virualItem{
            width: 100px;
        }
        .cover{
            position: absolute;
            top: 0;
            width: 100%;
            z-index: 2;
            visibility: hidden;
        }
    </style>


随机浏览