Vue滚动组件 非连续的 无限滚动

174 阅读1分钟

Vue滚动组件 非连续的 无限滚动

手写一个通用的滚动组件,可以很方便的使用。

功能

拿到插槽元素列表,通过自增索引,实现无限的滚动列表。

鼠标进入后停止滚动。

组件实现

<script lang="ts" setup>
import {computed, nextTick, onMounted, onUnmounted, ref, watch} from "vue";

defineOptions({
  name: 'VirtualScrolling'
})

const props = withDefaults(defineProps<{
  // 容器高度
  height: string,
  // 行数
  lines: number,
  // 动画间隔
  interval?: number,
}>(), {
  interval: 2000,
})

const slots = defineSlots<{
  default(): any
}>()

// 容器
const scrollContainerRef = ref<HTMLDivElement>()

// 重新生成子元素
const slotDef = computed(() => {
  const [def] = slots.default();
  let children = def.children
  let index = currentIndex.value;
  return [
    ...children.slice(index),
    ...children.slice(0, index)
  ]
})

const currentIndex = ref(0)
let scrollTimeout: number
const scrollAnimate = () => {
  const listElm = scrollContainerRef.value!.children

  console.log("scrollAnimate",listElm.length)
  // 长度不足 不滚动
  if (listElm.length <= props.lines){
    return
  }

  // 向下滚动
  scrollContainerRef.value!.scrollTo({
    top: listElm[1].offsetTop,
    behavior: "smooth",
  })
  scrollTimeout = setTimeout(() => {
    const [defaultSlots] = slots.default()

    currentIndex.value = (currentIndex.value + 1) % (defaultSlots.children!.length as number);
    scrollContainerRef.value!.scrollTo({
      top: 0,
      behavior: 'instant'
    })

    scrollAnimate()
  }, props.interval)
}
const initAnimate = () => {
  currentIndex.value = 0;
  startAnimate()
}
const startAnimate = () => {
  stopAnimate()
  // 等待元素更新
  nextTick(()=>{
    scrollAnimate()
  })
}
const stopAnimate = () => {
  clearTimeout(scrollTimeout)
}

onMounted(() => {
  initAnimate()
})

onUnmounted(() => {
  stopAnimate()
})


// 鼠标进入
const handlerMouseenter = () => {
  stopAnimate()
}

// 鼠标离开
const handlerMouseLevel = () => {
  startAnimate()
}

// 列表更新初始化
watch(()=>slots.default(),()=>{
  initAnimate()
})

</script>

<template>
  <div ref="scrollContainerRef" :style="{height: props.height}" class="scrolling"
       @mouseenter="handlerMouseenter"
       @mouseleave="handlerMouseLevel"
  >
    <component v-for="item in slotDef" :is="item" :key="item.key"></component>
  </div>
</template>

<style lang="less">
.scrolling {

  overflow: auto;
  position: relative;

  &::-webkit-scrollbar {
    display: none;
  }

}

</style>

使用

只实现插槽内是for循环的情况,插槽内元素必须加 key

<template>
    <VirtualScrolling height="100px" :lines="5">
      <MyComp v-for="o in options" :options="o" :key="o._key" height="20px"/>
    </VirtualScrolling>
</template>

插槽内不可以写注释,像下边这样,会出问题,没有做处理,不要这样写

<template>
    <VirtualScrolling height="100px" :lines="5">
      <!--这是一个注释-->
      <MyComp v-for="o in options" :options="o" :key="o._key" height="20px"/>
    </VirtualScrolling>
</template>