Некоторое время назад я написал статью об использовании IntersectionObserver API, где я использовал сервис Angular, чтобы решить, какое изображение наблюдать, и он отлично работал для веб-сайта Джоанны, что было довольно простым вариантом использования. . У меня был другой вариант использования, когда я хочу лениво загружать закадровые изображения - веб-приложение под названием Наши заметки позволяет создать группу и добавить / удалить заметку с дополнительным изображением. Все заметки и изображения хранятся и обслуживаются на firebase облачной платформе Google. Итак, в моем шаблоне есть ngFor цикл для отображения всех заметок в выбранной группе:

Как и ожидалось, загрузка страницы замедлялась по мере увеличения количества прикрепленных изображений. Предполагая, что основным узким местом является загрузка изображений, я потратил некоторое время, чтобы попробовать отдельный сервис, предназначенный для наблюдения за пересекающимися элементами - тот же подход, который я использовал в предыдущей статье, но вскоре отказался. Основная проблема заключалась в том, что мне приходилось вести список, похожий на исходный, noteService.items$, чтобы помнить, какие элементы имеют изображение, какой элемент сейчас просматривается и какой элемент наблюдать следующим, когда текущий пересекается . Я пришел к выводу, что, поскольку элемент и прикрепленное к нему изображение могут динамически изменяться под действием пользователя, было непрактично иметь другой список для отложенной загрузки.

Улучшение ng-defer-load

Затем я нашел эту небольшую библиотеку, которая предоставляет директиву deferLoad для ленивой загрузки элемента. Хорошая особенность этой библиотеки заключается в том, что, поскольку это директива, она имеет прямой доступ к своему элементу хоста для наблюдения и может зависеть от хуков жизненного цикла, таких как ngOnInit и ngOnDestroy, для регистрации API и отмены регистрации обратного вызова. Напротив, в сервисном подходе сервис и каждый элемент должны каким-то образом взаимодействовать, чтобы регистрация / деградация происходили должным образом синхронно с динамическими изменениями в списке. К сожалению, когда я попробовал эту директиву в своем списке, я вскоре обнаружил, что она вообще не действует, все изображения были загружены сразу. Оказалось, что время регистрации API нужно было немного отложить, чтобы обойти проблему. Я предполагаю, что анимация, которая была у меня на каждом элементе, могла быть причиной проблемы. Вы можете увидеть проблему в демонстрации Stackblitz, переключив deferLoad.



Чтобы отложить регистрацию API, я решил ввести новый LazyLoadService и позволить ему объявить приказ о регистрации для всех директив с задержкой.

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

Затем родительский компонент, на котором размещен список, попросит службу отложить регистрацию на 1500 мс, например, при ее создании.

Теперь проблема исчезла, изображения загружаются медленно, пока страница прокручивается вниз. Еще я хотел улучшить то, что директива безоговорочно регистрирует API, тогда как на самом деле изображение заметки было необязательным, поэтому для заметок без изображения директиву не нужно регистрировать в первую очередь. Поэтому я добавил в директиву необязательный ввод [url], чтобы обойти регистрацию при создании, если у нее нет URL-адреса.

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

Я закончил тем, что добавил в директиву еще один необязательный ввод [index] и позволил ему объявить свой индекс, когда он пересекается, в то же время каждая директива будет подписываться на announcedIntersection observable для сравнения объявленного пересекающегося индекса со своим собственным индексом, если разница достаточно мала, это означает, что он близок к отображению на экране, поэтому пора загружаться.

LazyLoadService помогает директивам взаимодействовать друг с другом, предоставляя announceIntersection метод, который будет вызываться пересекающимся элементом, и announcedIntersection наблюдаемый для подписки всеми директивами, он также имеет свойство loadAheadCount со значением по умолчанию 2, которое может быть перезаписано родительским компонентом при создании, чтобы контролировать количество изображений загружать перед пересекающимся элементом.

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

Резюме

Я показал здесь, как я расширил ng-defer-load, чтобы сделать его полезным для более широких приложений, добавив следующие параметры, он отлично работал для моего приложения, поэтому я опубликовал его как другой пакет npm под названием ng-lazy-load.

  • LazyLoadService, чтобы отложить время регистрации API
  • дополнительный [url] вход для обхода регистрации для некоторого увеличения производительности
  • дополнительный [index] вход для управления временем загрузки

ng-lazy-load library разработана как один из дочерних проектов приложения Наши заметки, и полный исходный код можно найти здесь.

Примечание. На данный момент API IntersectionObserver изначально не поддерживается в некоторых браузерах, таких как iOS Chrome и Safari, поэтому вам необходимо выполнить полифил, добавив Intersection-Observer.js в ваш angular.json.

Спасибо за прочтение.