Когда данные выпадающего списка слишком велики, как с этим бороться?

JavaScript Element Vue.js Ajax

предисловие

За ним код и демо-адрес

В повседневной разработке, помимо использования готовых плагинов, есть еще много задач, которые можно решить только самостоятельно. Сначала бросьте проблему.Когда данные выпадающего списка достигают тысяч или даже десятков тысяч, браузер серьезно застрянет. Взгляните на пример ниже

demo

Как показано на рисунке, объем данных достигает2Wполоскапростые тестовые данные(на странице больше ничего нет), щелчок по раскрывающемуся списку загрузки занял около5sвремя. Когда такое происходит, у меня на душе очень тяжело, разве это не играет со мной?

Решения

Эта проблема на самом деле является той же проблемой производительности, что и табличные данные.Решение таблицы заключается в уменьшении объема данных, передаваемых страницей через пейджер. Итак, как решить выпадающий список? Обычно мы загружаем все данные, вытащенные за один раз.Для текущей проблемы идея та же, используя пейджинг для решения проблемы с производительностью страницы. Проблема возникает снова: пейджер можно щелкнуть, но нельзя щелкнуть раскрывающийся список, поэтому это важное событие может быть реализовано только путем прослушивания события прокрутки.

Сначала набросок:

  1. прокрутка монитора
  2. Загружать данные назад при прокрутке вниз
  3. Загружать данные вперед при прокрутке вверх
  4. данные в и из

начинается хорошая игра

1. Следите за прокруткой

    <el-select class="remoteSelect" v-scroll v-model="value">
      <el-option :value="item.id" v-for="item in list" :key="item.id">{{item.name}}</el-option>
    </el-select>

Здесь основано на vue и element-uiel-selectРеализована прокрутка монитора. Вот способ контролировать прокрутку с помощью пользовательских инструкций

// directives目录下index.js文件

import Vue from 'vue'
export default () => {
  Vue.directive('scroll', {
    bind (el, binding) {
      // 获取滚动页面DOM
      let SCROLL_DOM = el.querySelector('.el-select-dropdown .el-select-dropdown__wrap')
      SCROLL_DOM.addEventListener('scroll', function () {
        console.log('scrll')
      })
    }
  })
}

Зарегистрируйтесь и используйте глобальный метод Vue.use() в main.js

import Directives from './directives'
Vue.use(Directives)

В это время вы можете пролистать страницу, чтобы увидеть журнал контрольной печати, а это значит, что мониторинг вступил в силу.Тогда засучите рукава и приступайте к сушке.

2. Загружать данные назад при прокрутке вниз

Сначала определите, следует ли прокручивать вверх или прокручивать вниз

  1. Запишите последнюю позицию прокрутки
  2. Сравните текущую позицию с последней позицией прокрутки

Глобальное местоположение записывается через общедоступную переменную черезscrollTopМетод получает текущую позицию прокрутки и записывает ее в общедоступную переменную.scrollPositionвнутри

    bind (el, binding) {
      // 获取滚动页面DOM
      let SCROLL_DOM = el.querySelector('.el-select-dropdown .el-select-dropdown__wrap')
      let scrollPosition = 0
      SCROLL_DOM.addEventListener('scroll', function () {
        // 当前的滚动位置 减去  上一次的滚动位置
        // 如果为true则代表向上滚动,false代表向下滚动
        let flagToDirection = this.scrollTop - scrollPosition > 0
        // 记录当前的滚动位置
        scrollPosition = this.scrollTop
        console.log(flagToDirection ? '滚动方向:下' : '滚动方向:上')
      })
    }

В настоящее время направление прокрутки известно, и затем выполняется соответствующая обработка в соответствии с направлением прокрутки. Расскажите компоненту о поведении прокрутки

    ...省略
        // 记录当前的滚动位置
        scrollPosition = this.scrollTop
        // 将滚动行为告诉组件
        binding.value(flagToDirection)

принятие событиясуществуетv-scrollполучить событие в командеv-scroll="handleScroll", в методеhandleScrollОбработка поведения прокрутки. Далее вам нужно только запросить данные для прокрутки вниз в этом событии.

    /*********************************
      ** Fn: handleScroll
      ** Intro: 处理滚动行为
      ** @params: param 为true代表向下滚动
      ** @params: param 为false代表向上滚动
    *********************************/
    handleScroll (param) {
      if (param) {
        // 请求下一页的数据
        this.list.push(...this.ajaxData(++this.pageIndex))
      }
    },

Здесь реализована загрузка прокрутки. Просто загрузка слишком частая.Если скроллить быстро, будет выдаваться несколько запросов фоновых данных одновременно.В некоторых интенсивных браузерах ajax будет разрабатываться и ставиться в очередь одновременно, что не идеально. Как это контролировать? Запускается другим способомhandleScrollСобытие срабатывает, когда положение прокрутки находится на определенной высоте от низа прокручиваемой страницы, например, только от низа страницы100pxкурокhandleScrollсобытие

  1. scrollHeightполучить высоту прокрутки
  2. в 100px снизу
        // 记录当前的滚动位置
        scrollPosition = this.scrollTop
        const LIMIT_BOTTOM = 100
        // 记录滚动位置距离底部的位置
        let scrollBottom = this.scrollHeight - (this.scrollTop + this.clientHeight) < LIMIT_BOTTOM
        // 如果已达到指定位置则触发
        if (scrollBottom) {
          // 将滚动行为告诉组件
          binding.value(flagToDirection)
        }

Благодаря изменению длины данных мы можем узнать, что триггерное событие было явно гармоничным.Этот эффект аналогичен способу ленивой загрузки мобильных телефонов, и данные будут непрерывно накладываться.

намекать:Будет ошибка, что ajax является асинхронным, если этот запрос ajax занимает1sТолько когда данные будут возвращены, а прокрутка в это время продолжится, будут инициированы несколько событий запроса. Как избежать этой ситуации? Ответ заключается в том, чтобы добавить флаг, установить для него значение false перед запросом и установить его в значение true после запроса. Этот флаг проверяется первым для каждого запроса. Если false предотвращает событие.

полузащита

Взгляните на нашу схему

  1. прокрутка монитора
  2. Загружать данные назад при прокрутке вниз
  3. Загружать данные вперед при прокрутке вверх
  4. данные в и из

Пока мы выполнили только шаги ① и ②. Если ваши потребности удовлетворены, вы можете закончить чтение. Если это вам немного поможет, ставьте лайк и уходите.

То, что я сказал ранее, является лишь базовой операцией, и я еще не начал на ней сосредотачиваться. Как насчет отсутствия давления производительности?

  1. Кодовый адрес:github
  2. Демонстрация текущей версии:demo

Пойдем домой после работы на ужин. Продолжайте писать в выходные --2018-11-09 18:15


великолепная сегментация


как выходные,все придет, как ожидалось. --2018-11-10 08:30

3. Загружать данные вперед при прокрутке вверх

существуетhandleScrollсредний параметр оценкиparamМы изучили поведение прокрутки, но раньше мы ограничивали только время срабатывания прокрутки вниз, а теперь мы усовершенствовали время срабатывания прокрутки вверх. Точно так же сначала используйте расстояние от вершины100pxпри срабатывании.

пока текущая позиция прокруткиscrollTopменьше, чем100pxпросто вызватьhandleScrollсобытие

        // 如果向下滚动 并且距离底部只有100px
        if (flagToDirection && scrollBottom) {
          // 将滚动行为告诉组件
          binding.value(flagToDirection)
        }
        // 如果是向上滚动  并且距离顶部只有100px
        if (!flagToDirection && this.scrollTop < LIMIT_BOTTOM) {
          binding.value(flagToDirection)
        }

существуетhandleScrollВ случае, если мы уже можем обнаружить поведение прокрутки вверх, и время запуска соответствует ожидаемому.

Проблемы шли одна за другой, одна серьезнее другой. При прокрутке до конца нагрузка на страницы продолжает накапливаться, и данные с первой страницы до страницы, которая была прокручена и загружена, находятся в списке, так зачем вам нужно их загружать? Сначала заройте здесь дыру4. Данные приходят и уходятПрочитав эту яму, она будет решена.

4. Данные приходят и уходят

Как насчет отсутствия давления производительности? Прямо в этот момент. Взгляните на картинку одним взглядом (эту визуализацию найти непросто):

  1. Прокрутите вниз (каждый щелчок на рисунке представляет собой триггер для прокрутки для загрузки данных)

2. Прокрутите вверх

3. Вход и выход

Как показано на картинке выше, это то, чего мы хотим достичь в конце. Прокрутка вверх загружает данные предыдущей страницы, а прокрутка вниз загружает прокрутку следующей страницы. объект данныхlistВсегда есть только определенный объем данных. Независимо от того, насколько велик объем данных, что я могу сделать?

Давайте посмотрим, как это сделать

Как сохранить длину этого массива? Легко сказать, что есть входы и выходы, но реализация непростая.

Предположим, что наш текущий контейнер массива может содержать до 4 страниц данных, по 100 фрагментов данных на страницу. пройти черезpageLimitПараметр для ограничения длины массива, который нам нужно поддерживать, здесь установлен равным 4.

Как мы узнаем, какая страница загружена в данный момент при прокрутке вниз или прокрутке вверх?

Итак, нам нужна таблица записейpageMapДля записи номера страницы таблица номеров страниц связана с текущим объектом данных.listсоответствовать. Корреспонденция следующим образом.

pageLimit: 4
pageMap: [1, 2, 3, 4]
list:['第一页的数据', '第二页的数据', '第三页的数据', '第四页的数据']

Рендеринг (в настоящее время прокрутка ненаучна, шаги правильные, за ними следует оптимизированная прокрутка):

    /*********************************
      ** Fn: handleScroll
      ** Intro: 处理滚动行为
      ** @params: param 为true代表向下滚动
      ** @params: param 为false代表向上滚动
    *********************************/
    handleScroll (param) {
      if (param) {
        if (this.pageMap.length >= this.pageLimit) {
          // 当长度相等的时候, 绝对不能超出长度  则有进必有出
          // 删除 pageMap 列表的第一个元素
          this.pageMap.shift()
          // 对应删除list中一页的数据量
          this.list.splice(0, this.pageSize)
        }
        ++this.pageIndex
        this.pageMap.push(this.pageIndex)
        // 请求下一页的数据
        this.list.push(...this.ajaxData(this.pageIndex))
        // 同步记录页码
      } else {
        // 如果在向上滚动时,如果还没有到达第一页则继续加载。 如果已到达则停止加载
        if (this.pageMap[0] > 1) {
          // 向上滚动,取出pageMap中第一个元素值减1
          this.pageIndex = this.pageMap[0] - 1
          // 同步设置分页
          // ①先删除最后一个元素
          this.pageMap.pop()
          // ②将新元素添加在头部
          this.pageMap = [this.pageIndex, ...this.pageMap]
          // ①删除list中最后一页的数据
          this.list.splice(-this.pageSize, this.pageSize)
          // ②将新数据添加在头部位置
          this.list = [...this.ajaxData(this.pageIndex), ...this.list]
        } else return false
      }
    }

Давайте сначала сюда напишем, опять пора обедать 2018-11.10 12:01

Добрый день, зимнее солнце пригревает~ 2018-11-10 13:04

Оптимизировать прокрутку

Далее заполним яму, оставленную выше.Когда данные достигнут заданной длины, общий объем данных не изменится, тогда общая высота прокруткиscrollHeightЭто также исправлено Это то, что, хотя данные входят и выходят, это не влияет на положение прокрутки.scrollTopЭто больше не будет иметь никакого влияния.Например, эффект на динамической картинке выше будет прокручиваться до конца, но в это время это не конец страницы, но это заставляет пользователей ошибочно думать, что это в конце~ ~ Эта проблема немного серьезна, немного серьезна~

Оптимизация:

  1. Возвращайте текущую позицию прокрутки в середину общей высоты прокрутки после каждой загрузки данных. В этот момент нам нужно прокрутитьdomи высота среднего положения через пользовательскую директивуv-scrollБрошенный, при добавлении данных в голову или добавлении данных в хвост положение прокрутки позиционируется в среднее положение.

Выбрасывает DOM и прокручивает среднюю позицию

// directives目录下index.js文件
        // 如果向下滚动 并且距离底部只有100px
        if (flagToDirection && scrollBottom) {
          // 将滚动行为告诉组件
          binding.value(flagToDirection, SCROLL_DOM, this.scrollHeight / 2)
        }
        // 如果是向上滚动  并且距离顶部只有100px
        if (!flagToDirection && this.scrollTop < LIMIT_BOTTOM) {
          binding.value(flagToDirection, SCROLL_DOM, this.scrollHeight / 2)
        }

когдаpageMap(соответствоватьlistдлина) доpageLimitДлина, сброс положения прокрутки DOM при добавлении или удалении данных.

    /*********************************
      ** Fn: handleScroll
      ** Intro: 处理滚动行为
      ** @params: param 为true代表向下滚动 为false代表向上滚动
      ** @params: el 滚动DOM
      ** @params: middlePosition 滚动列表的中间位置
    *********************************/
    handleScroll (param, el, middlePosition) {
      if (param) {
        if (this.pageMap.length >= this.pageLimit) {
          ....省略代码
          this.list.splice(0, this.pageSize)
          // 回滚到中间位置
          el.scrollTop = middlePosition
        }
        ....省略代码
      } else {
        // 如果在向上滚动时,如果还没有到达第一页则继续加载。 如果已到达则停止加载
        if (this.pageMap[0] > 1) {
            ....省略代码
          this.list = [...this.ajaxData(this.pageIndex), ...this.list]
          // 回滚到中间位置
          el.scrollTop = middlePosition
        } else return false
      }
    },

Эффект как показано выше, должен ли он закончиться? Студенты, которые внимательно наблюдали, снова нашли пасхальные яйца. В момент прокрутки и перехода данные, которые видит первоначальный пользователь, уже не являются данными, которые первоначальный пользователь видит после перехода.да я..... Эта проблема немного серьезная,паника

2. Оптимизация критической точки качения критическая точка - дальность каченияОбщая высотаКогда верх или низ находится на определенном расстоянии, срабатывает критическая точка handleScroll, то есть константаLIMIT_BOTTOM. ранее определенныйconst LIMIT_BOTTOM = 100Для 100 в этом нет смысла, так что подойдите к серьезной критической точке.

Состояние расчесывания

  1. каждый раз возвращаться к1/2 scrollHeightпозиция
  2. Позиция каждого изменения данных(1 / pageLimit) * scrollHeight, что показано здесь1/4 * scrollHeight
  3. установить неизвестноеXкритическая точка для прыжка
  4. Переломный момент — это то, что видит пользователь, прежде чем прыгнуть.
  5. 1/2 scrollHeightэто позиция после прыжка пользователя

Выражение: x + (1/4 * scrollHeight) = (1/2 scrollHeight)

x = (1/4 * scrollHeight), т.е.const LIMIT_BOTTOM = this.scrollHeight / 4Затем снова откроем его и посмотрим на ситуацию с прокруткой:

Эффект почти тот же.Если вы хотите, чтобы пользователь видел позицию ниже в конце, вы можете установитьconst LIMIT_BOTTOM = this.scrollHeight / 4.2

Эпилог

История наконец заканчивается здесь.поставить лайкПодбодри меня~

Создайте новый репозиторий на github для загрузки кода:

  1. демонстрационный вид:demo
  2. просмотр кода github:портал
  3. Это очень полезно, но никто этого не ценитПользовательская директива Vue реализует ограничение ввода положительное целое число
  4. Зачем переупаковывать AJAX?
  • Уведомление об авторских правах: Эта статья была впервые опубликована в Nuggets, если вам нужно перепечатать, пожалуйста, укажите источник.

Лучшее время посадить дерево было десять лет назад, затем сейчас. -- Неважно, кто это сказал.