Vue+TS开发定长虚拟列表

1.定长虚拟列表

  1. 定义:虚拟列表也有叫无限滚动的,创建一个滚动列表来渲染大量数据

  2. 使用场景有大量数据需要渲染时。

  3. **解决了什么问题?**大量数据需要渲染,例如Vue传统使用V-for遍历会创建大量Node节点,对于内存和渲染时间的占用都是非常大的。

  4. 怎么做?

    1. 虽然数据量很大但是我们要展示给用户的并不是全部数据,因此我们只需要根据每个元素高度和视口高度渲染元素即可。
    2. 然后监听滚动,当滚动时动态切换数据
    3. 将内容盒子随着滚动进行Y轴的偏移
  5. HTML和css结构

    1. <div class="container" :style="container_style">
          <!-- 容器盒子-》监听该容器的滚动事件 -->
          <div class="scroll_container" ref="scroll_ref">
            <!-- 撑起盒子高度 -->
            <div class="pillar" ></div>
             <!-- 内容列表用来渲染数据的盒子 -->
            <div class="items" ref="items_ref" >
              <div :style="item_style" v-for="item in renderData" key="item" class="item">{{ item }}</div>
            </div>
          </div>
        </div>
      
    2. css使用动态属性去绑定结构很清晰,可扩展性很强
      <style lang="scss" scoped>
       .container{
        width: v-bind('props.style.width');
        height: v-bind('props.style.height');
        background: v-bind('props.style.background');
        .scroll_container::-webkit-scrollbar {
          width: 10px; /* 滚动条的宽度 */
          background-color: #f5f5f5; /* 滚动条的背景颜色 */
        }
        .scroll_container::-webkit-scrollbar-thumb {
          background-color: greenyellow; /* 滚动条拖动块的颜色 */
        }
        .scroll_container{
          position: relative;
          overflow: auto;
          -webkit-overflow-scrolling:touch;
          width: 100%;
          height: 100%;
          .pillar{
            position: absolute;
            right: 0;
            left: 0;
            top: 0;
            z-index: -1;
            height: v-bind('scroll_height');
          } 
          .items{
            height: 100%;
            z-index: 10;
            top: 0;
            left: 0;
            right: 0;
            position: absolute;
            box-sizing: border-box;
            .item{
              width: 100%;
              line-height: calc(v-bind('props.item_height') * 1px);
              height: calc(v-bind('props.item_height') * 1px);
              box-sizing: border-box;
            }
          }
        }
       }
      </style>
      
  6. Vue核心部分

    1. interface IVirtualListPropsStyle {
          width: string
          height: string
          background: string
      }
      
      interface IVirtualListProps {
        style?: IVirtualListPropsStyle
        data: any[],
        show_size?: number
        item_height?: number,
        item_style?: Record<string, any>
        container_style?:Record<string, any>
      }
      
      
      const props = withDefaults(defineProps<IVirtualListProps>(), {
        style: () => {
          return {
            width: '400px',
            height: '200px',
            background:'skyblue'
          }
        },
        data: ()=>[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
        show_size: 5,
        item_height:40
      })
      const scroll_ref = ref<HTMLElement|null>(null)
      const items_ref = ref<HTMLElement|null>(null)
      
      const {
        scroll_height,
        renderData
      } = useVirtualList(props,scroll_ref as Ref<HTMLElement>,items_ref as Ref<HTMLElement>)
      
      
    2. hooks-》useVirtualLit

      import { computed, onMounted, ref, type Ref } from "vue"
      export default (props: IVirtualListProps, scroll_ref: Ref<HTMLElement> | null, items_ref: Ref<HTMLElement> | null) => {
          const scrollTop = ref(0)
          // 定高虚拟列表的当前页展示个数 = 内容容器高度 / 单个内容高度
          const show_size = computed(() => Math.ceil(parseInt(props.style.height) / props.item_height))
          // 总高度 每个数据的高度×总数据个数
          const scroll_height = computed(() => {
              if (props.data.length === 0) {
                  console.warn(`Not received props.data`)
                  return 0
              }
              return (props.data.length - 1) * props.item_height + 'px'
          })
          // 切片的起始和结束索引
          const start_index = ref(0)
          const end_index = ref(show_size.value)
          // 切片渲染数据
          const renderData = computed(() => props.data.slice(start_index.value, end_index.value))
      
          onMounted(() => {
              scroll_ref!.value.addEventListener('scroll', (e: Event) => {
                  // show_size.value * props.item_height -》从滚动开始计算
                  scrollTop.value = (e.target as HTMLElement).scrollTop + show_size.value * props.item_height
                  // 滚动改变索引的偏置
                  const bias = Math.ceil(scrollTop.value / props.item_height)
                  start_index.value += bias - end_index.value
                  end_index.value = end_index.value + (bias - end_index.value)
                  // 内容容器Y的偏移量
                  items_ref!.value.style.transform = `translateY(${(e.target as HTMLElement).scrollTop}px)`
              })
          })
          return {
              scroll_height,
              renderData
          }
      }
      
  7. 结果展示:内容的DOM节点始终只有五个但是渲染的数据很大
    在这里插入图片描述
    体验直接下载 pnpm install fancy_virtual_list 即可,具体功能可看md

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,我可以为您提供一个简单的实现方案。首先,您需要创建一个 Vue 组件,可以命名为 MultiSelect。 MultiSelect 组件需要包含一个 input 输入框和一个下拉菜单,下拉菜单中包含多个选项,每个选项都可以被选择或取消选择。 下面是 MultiSelect 组件的基本代码: ```html <template> <div class="multi-select"> <div class="input-container"> <input type="text" v-model="searchKeyword" @input="filterOptions" @focus="isFocus = true" @blur="isFocus = false" placeholder="请选择"> <i class="el-icon-arrow-down"></i> </div> <div class="options-container" v-show="isFocus"> <ul> <li v-for="(option, index) in filteredOptions" :key="index" @click="toggleOption(option)"> <span v-if="selectedOptionIndexes.indexOf(index) !== -1" class="el-tag el-tag--info">{{ option.label }}</span> <span v-else>{{ option.label }}</span> </li> </ul> </div> </div> </template> <script> export default { name: 'MultiSelect', props: { options: { type: Array, required: true } }, data () { return { searchKeyword: '', isFocus: false, selectedOptionIndexes: [] } }, computed: { filteredOptions () { return this.options.filter(option => option.label.includes(this.searchKeyword)) } }, methods: { filterOptions () { // 根据搜索关键词过滤选项 }, toggleOption (option) { // 切换选项的选中状态 } } } </script> <style scoped> .multi-select { position: relative; display: inline-block; width: 200px; } .input-container { border: 1px solid #ccc; border-radius: 4px; padding: 6px; display: flex; justify-content: space-between; align-items: center; cursor: pointer; } .input-container input { border: none; outline: none; flex: 1; } .options-container { position: absolute; top: 100%; left: 0; width: 100%; border: 1px solid #ccc; border-top: none; background-color: #fff; z-index: 999; } .options-container ul { list-style: none; margin: 0; padding: 0; } .options-container li { padding: 6px; cursor: pointer; } .options-container li:hover { background-color: #f5f5f5; } .el-tag { margin-right: 6px; } </style> ``` 上面的代码中,props 中的 options 数组包含了所有可选项,每个选项由 label 和 value 两个属性组成。搜索关键词通过 v-model 绑定到 input 输入框中,并且通过 @input 事件实时更新过滤后的选项列表。下拉菜单的显示和隐藏通过 v-show 和 isFocus 控制。选中的选项通过 selectedOptionIndexes 数组保存,toggleOption 方法用于切换选项的选中状态。 如果您需要实现多选功能,可以在 toggleOption 方法中修改为下面的代码: ```javascript toggleOption (option) { const index = this.selectedOptionIndexes.indexOf(option); if (index !== -1) { this.selectedOptionIndexes.splice(index, 1); } else { this.selectedOptionIndexes.push(option); } } ``` 这样,选中的选项就可以通过 selectedOptionIndexes 数组保存,然后可以在其他组件中通过 props 或 emit 事件获取到。 希望这个简单的实现方案可以帮助到您。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大鲤余

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值