Тема этой статьи
Отрисовка длинных списков и бесконечные выпадающие списки также являются одной из распространенных проблем во фронтенд-разработке.Лаконично, гениально, эффективноспособ достижения. Без лишних слов, посмотрите на картинку ниже, может что-то найдете?
Интересно, заметили ли вы что-нибудь из приведенной выше картинки, например, рендерится только часть DOM видимой области, а в процессе прокрутки меняется только отступ внешнего контейнера?
Первый пункт легко понять, учитывая производительность, невозможно отобразить все элементы списка длинного списка (даже бесконечный выпадающий список); последний пункт является одним из основных решений, представленных в этой статье!
Чтобы не продаваться, скажу заранее, что элементов плана два:
- Intersection Observer
- padding
Элементы объясняются, возможно, вы можете попытаться начать думать и посмотреть, сможете ли вы угадать конкретную реализацию.
Введение
Intersection Observer
Базовые концепты
все это время,Обнаружение визуального состояния элемента или относительного визуального состояния двух элементовНи то, ни другое не легко. Традиционные решения не только сложны, но и требуют больших затрат на производительность: например, нужно отслеживать события прокрутки, затем запрашивать DOM, получать высоту и положение элементов, вычислять высоту из окна и так далее.
Эту проблему решает Intersection Observer. Он предоставляет разработчикам новый удобный способАсинхронный запросПоложение элемента относительно других элементов или видовых экранов, исключающее затраты на дорогостоящие поиски в DOM и чтение стилей.
совместимость
В основном в Safari совместимость плохая, для совместимости нужна версия 12.2 и выше, но, к счастью, естьpolyfillСъедобный.
некоторые сценарии применения
- Реализация ленивой загрузки при прокрутке страницы.
- Бесконечный выпадающий список (реализация этой статьи).
- Отслеживайте показ определенных рекламных элементов, чтобы вести соответствующую статистику.
- Отслеживайте, достигло ли пользовательское поведение прокрутки целевой позиции, чтобы реализовать некоторую интерактивную логику (например, приостановку воспроизведения, когда элемент видео прокручивается в скрытую позицию).
Реализация схемы заполнения
После базового понимания Intersection Observer давайте посмотрим, как использовать Intersection Observer + padding для достижения бесконечного раскрывающегося списка.
Давайте рассмотрим общую идею:
- Контролировать, входят ли в окно элементы начала и конца списка фиксированной длины;
- Обновите серийный номер, соответствующий первому элементу, отображаемому на текущей странице;
- В соответствии с вышеуказанным порядковым номером получается целевой элемент данных, и содержимое списка повторно визуализируется в соответствующее содержимое;
- Заполнение контейнера регулируется для имитации прокрутки.
Ядро: используйте заполнение родительского элемента, чтобы заполнить все больше и больше элементов DOM, которые должны были быть с бесконечным раскрывающимся списком, и сохраните только определенное количество элементов DOM выше и ниже области просмотра для рендеринга данных..
1. Следите за тем, входят ли в окно элементы начала и конца списка фиксированной длины.
// 观察者创建
this.observer = new IntersectionObserver(callback, options);
// 观察列表第一个以及最后一个元素
this.observer.observe(this.firstItem);
this.observer.observe(this.lastItem);
Давайте возьмем пример рендеринга фиксированных 20 элементов списка на странице.Мы используем Intersection Observer для наблюдения за первым элементом и последним элементом.Когда один из них повторно входит в область просмотра, будет запущена функция обратного вызова:
const callback = (entries) => {
entries.forEach((entry) => {
if (entry.target.id === firstItemId) {
// 当第一个元素进入视窗
} else if (entry.target.id === lastItemId) {
// 当最后一个元素进入视窗
}
});
};
2. Обновите порядковый номер, соответствующий первому элементу, отображаемому на текущей странице (firstIndex).
В качестве конкретного примера мы используем массив для хранения данных, которые необходимо отобразить на странице. Длина массива увеличивается по мере запроса новых данных, и всегда отображается определенное количество элементов, скажем, 20. Так:
-
1. Изначально рендерятся элементы с порядковыми номерами от 0 до 19 в массиве, то есть соответствующий firstIndex в это время равен 0;
-
2. Когда элемент с порядковым номером 19 (то есть последнийItem на предыдущем шаге) входит в окно, мы будем рендерить 10 элементов назад, то есть элемент с порядковым номером 10 - 29, затем первыйIndex на этом время 10;
-
3. В следующий раз, когда элемент с порядковым номером 29 входит в вьюпорт, продолжать рендерить 10 элементов назад, то есть рендерится элемент с порядковым номером 20 - 39, то firstIndex в это время равен 20, и так далее. . .
// 我们对原先的 firstIndex 做了缓存
const { currentIndex } = this.domDataCache;
// 以全部容器内所有元素的一半作为每一次渲染的增量
const increment = Math.floor(this.listSize / 2);
let firstIndex;
if (isScrollDown) {
// 向下滚动时序号增加
firstIndex = currentIndex + increment;
} else {
// 向上滚动时序号减少
firstIndex = currentIndex - increment;
}
В общем, обновление firstIndex должно знать, какие данные должны быть извлечены и отображены следующими в соответствии с прокруткой страницы.
3. В соответствии с приведенным выше серийным номером получается соответствующий элемент данных, и список перерисовывается в новый контент.
const renderFunction = (firstIndex) => {
// offset = firstIndex, limit = 10 => getData
// getData Done => new dataItems => render DOM
};
Эта часть предназначена для запроса данных в соответствии с firstIndex, а затем отображения целевых данных на странице.
4. Регулировка заполнения для имитации прокрутки
Теперь, когда мы добились обновления данных и элементов DOM, как нам добиться эффекта бесконечного раскрывающегося списка и прокрутки?
Представьте, кроме всего прочего, самый примитивный, прямой и грубый способ — это не что иное, как то, что после получения 10 новых элементов данных мы подключаем 10 новых DOM-элементов к странице для рендеринга этих данных.
Но на данный момент, по сравнению с грубой схемой выше, наша схема такова:Эти 10 новых элементов данных мы используем для рендеринга исходных элементов DOM, заменяем невидимые элементы данных, покинувшие область просмотра, а часть, которая должна быть дополнительно расширена дополнительными элементами DOM, мы используем отступы Padding для имитации реализации.
- прокрутить вниз
// padding的增量 = 每一个item的高度 x 新的数据项的数目
const remPaddingsVal = itemHeight * (Math.floor(this.listSize / 2));
if (isScrollDown) {
// paddingTop新增,填充顶部位置
newCurrentPaddingTop = currentPaddingTop + remPaddingsVal;
if (currentPaddingBottom === 0) {
newCurrentPaddingBottom = 0;
} else {
// 如果原来有paddingBottom则减去,会有滚动到底部的元素进行替代
newCurrentPaddingBottom = currentPaddingBottom - remPaddingsVal;
}
}
- прокрутить вверх
// padding的增量 = 每一个item的高度 x 新的数据项的数目
const remPaddingsVal = itemHeight * (Math.floor(this.listSize / 2));
if (!isScrollDown) {
// paddingBottom新增,填充底部位置
newCurrentPaddingBottom = currentPaddingBottom + remPaddingsVal;
if (currentPaddingTop === 0) {
newCurrentPaddingTop = 0;
} else {
// 如果原来有paddingTop则减去,会有滚动到顶部的元素进行替代
newCurrentPaddingTop = currentPaddingTop - remPaddingsVal;
}
}
- Наконец, обновление настроек заполнения и соответствующее обновление данных кэша.
// 容器padding重新设置
this.updateContainerPadding({
newCurrentPaddingBottom,
newCurrentPaddingTop
})
// DOM元素相关数据缓存更新
this.updateDomDataCache({
currentPaddingTop: newCurrentPaddingTop,
currentPaddingBottom: newCurrentPaddingBottom
});
Резюме мыслей
Краткое содержание программы:
Используйте Intersection Observer для отслеживания положения прокрутки связанных элементов, асинхронного мониторинга, максимально возможного сокращения операций DOM, запуска обратных вызовов, а затем получения новых данных для обновления элементов страницы и настройки заполнения контейнера для замены все большего количества элементов DOM и наконец, добиться прокрутки списка и бесконечного раскрывающегося списка.
Сравнение родственных программ
Вот и более известные библиотеки -iScrollРеализованная схема бесконечного раскрывающегося списка делает базовое сравнение.Перед сравнением поясняется схема реализации бесконечного iScroll:
-
iScroll получает расстояние прокрутки, прослушивая традиционные события прокрутки, а затем:
- Установите перевод родительского элемента для перемещения всего содержимого вверх (вниз);
- Затем рассчитайте соответственно на основе этого расстояния прокрутки, знайте, что соответствующие подэлементы были прокручены за пределы области просмотра, и оцените, следует ли переместить эти подэлементы, которые покинули область просмотра, в конец, а затем установите их для перевода в двигаться до конца. Это похоже на циклическую очередь: по мере прокрутки верхний элемент сначала выходит из окна, но перемещается в конец, обеспечивая бесконечное выпадающее меню.
-
Связанные сравнения:
- Сравнение реализации: первый — это мониторинг Intersection Observer для уведомления дочернего элемента о выходе из окна, если заполнение родительского элемента задано количественно; другой — мониторинг традиционных событий прокрутки, получение расстояния прокрутки, и ряд вычислений для установки родительского элемента и перевода дочернего элемента. Очевидно, что первое выглядит более лаконично и ясно.
- Сравнение производительности: я знаю, что когда дело доходит до сравнения, у вас в голове должны возникать проблемы с производительностью. Фактически, ключом к сравнению производительности является Intersection Observer. Какая разница в производительности только из-за настройки заполнения или настройки перевода, но я лично чувствую, что заполнение будет проще? Intersection Observer фактически абстрагирует всю связанную логику уровня прокрутки. Вам больше не нужно получать соответствующие свойства DOM, такие как расстояние прокрутки, и вам больше не нужно выполнять серию сложных вычислений, связанных с расстоянием прокрутки и синхронной прокруткой. триггер события становится асинхронным, вам больше не нужно выполнять дополнительную логику, такую как защита от сотрясений, которая по-прежнему улучшается с точки зрения производительности.
Существующие дефекты:
- Расчет заполнения зависит от фиксированной высоты элемента списка.
- Это синхронная схема рендеринга, то есть текущий расчет и настройка заполнения контейнера не могут вычислять данные, полученные асинхронно, и связаны только с поведением пользователя при прокрутке. Это выглядит немного не в соответствии с реальным бизнес-сценарием. Решения:
- Идея 1. ИспользованиеSkeleton Screen LoadingДля синхронного рендеринга элементов данных на него не влияет асинхронное получение данных. То есть, когда запрос данных не был завершен, некоторые изображения используются для заполнения пространства, а затем загружается содержимое, а затем заменяется.
- Идея 2. Прокрутите до целевой позиции, заблокируйте настройку заполнения контейнера (то есть появление бесконечного выпадающего списка) до тех пор, пока запрос данных не будет завершен, и используйте загружаемый gif, чтобы запросить у пользователя статус загрузки. , это решение относительно сложное, и вам необходимо полностью учитывать непредсказуемое поведение пользователя при прокрутке.Установите заполнение контейнера.
расширение
- Подумайте, пожалуйста, есть бесконечное подтягивание, так как же настроить реализацию бесконечного подтягивания на основе этой схемы?
- Если в iScroll используется Intersection Observer, как можно оптимизировать исходное решение?
Код
Справочная статья
- Intersection Observer API
- Появление IntersectionObserver
- Бесконечная прокрутка в правильном направлении
Эта статья была опубликована сКоманда внешнего интерфейса NetEase Cloud Music, Любое несанкционированное воспроизведение статьи запрещено. Мы всегда нанимаем, если вы готовы сменить работу и вам нравится облачная музыка, тоПрисоединяйтесь к нам