Бесконечная прокрутка

Vs Vue3 Select не имеет функционала бесконечной прокрутки списка, но ее можно реализовать, при помощи событий open, close и search, параметра filterable и слота list-footer.

Разберем пример приведенный ниже. Начнем с data:

  • observer - новый объект IntersectionObserver с infiniteScroll установленной в качестве функции обратного вызова
  • limit - количество отображаемых опций
  • search - встроенную фильтрацию компонента отключили через параметр, поэтому необходимо хранить строку поиска

Когда откроется выпадающий список, будет выдано событие open и будет вызван onOpen. Мы ждем $nextTick(), чтобы нужный нам $ref существовал, затем начинаем наблюдать за ним на предмет пересечения.

Наблюдатель настроен на вызов InfiniteScroll, когда <li> полностью виден в списке. Здесь выполняется некоторая деструктуризация, чтобы получить первый наблюдаемый элемент и, в частности, свойства isIntersecting и target. Если <li> пересекается, мы увеличиваем ограничение и обеспечиваем, чтобы положение прокрутки оставалось там, где оно было до изменения размера списка. Опять же, здесь важно дождаться $nextTick, чтобы элементы DOM были вставлены перед установкой положения прокрутки.

Вы могли бы создать observer непосредственно в data(), но поскольку рендер страницы может на стороне сервера, IntersectionObserver не существует в этой среде, поэтому нам нужно сделать это в mounted().

<template>
  <v-select
      :options="paginated"
      :filterable="false"
      @open="onOpen"
      @close="onClose"
      @search="(query) => (search = query)"
  >
    <template #list-footer>
      <li v-show="hasNextPage" ref="load" class="loader">
        Loading more options...
      </li>
    </template>
  </v-select>
</template>

<script>
import countries from '../data/countries'

export default {
  name: 'InfiniteScroll',
  data: () => ({
    observer: null,
    limit: 10,
    search: '',
  }),
  computed: {
    filtered() {
      return countries.filter((country) => country.includes(this.search))
    },
    paginated() {
      return this.filtered.slice(0, this.limit)
    },
    hasNextPage() {
      return this.paginated.length < this.filtered.length
    },
  },
  mounted() {
    this.observer = new IntersectionObserver(this.infiniteScroll)
  },
  methods: {
    async onOpen() {
      if (this.hasNextPage) {
        await this.$nextTick()
        this.observer.observe(this.$refs.load)
      }
    },
    onClose() {
      this.observer.disconnect()
    },
    async infiniteScroll([{isIntersecting, target}]) {
      if (isIntersecting) {
        const ul = target.offsetParent
        const scrollTop = target.offsetParent.scrollTop
        this.limit += 10
        await this.$nextTick()
        ul.scrollTop = scrollTop
      }
    },
  },
}
</script>

<style scoped>
.loader {
  text-align: center;
  color: #bbbbbb;
}
</style>