Введение
Обычно ресурсы изображений в HTML будут загружаться последовательно сверху вниз, а некоторые изображения можно будет увидеть только когда пользователь прокручивает страницу вниз, иначе трафик этой части изображения будет потрачен впустую.
Поэтому для тех веб-сайтов с большим количеством ресурсов изображений будет принят метод «загрузки по запросу», то есть ресурсы изображений будут загружаться только тогда, когда они появляются в области просмотра, что может повлиять на работу пользователя. немного, но может Значительно экономит трафик сайта.
И упомянутый выше метод «загрузки по требованию» сегодня является главным героем — технология ленивой загрузки изображений.
2. Принцип
Технология ленивой загрузки изображений в основном определяет, загружается ли ресурс изображения, отслеживая, появляется ли контейнер ресурса изображения в области окна просмотра.
Затем ядро реализации технологии ленивой загрузки изображений заключается в том, как определить, что элемент находится в области области просмотра.
3. Прошлая жизнь
Идеяскорой реализации:
- Укажите изображение-заполнитель для целевого элемента и сохраните ссылку на реальное изображение в пользовательском атрибуте, обычно data-src;
- Слушайте события прокрутки, связанные с поведением пользователя при прокрутке;
- Используйте метод Element.getBoundingClientRect() в обработчике событий прокрутки, чтобы определить состояние пересечения целевого элемента и области просмотра;
- Когда состояние пересечения целевого элемента и области просмотра больше 0, назначьте ссылку на реальное изображение свойству src или свойству backgroundImage целевого элемента.
1. событие прокрутки
Событие прокрутки может срабатывать с высокой частотой, и согласно вышеизложенным идеям, в обработчике события прокрутки неизбежно будет происходить большое количество DOM-операций, что, скорее всего, сделает страницу не "гладкой как шелк", тогда необходимо Уменьшить частоту манипуляций с DOM.
Уменьшите частоту операций DOM, используя регулирование функций, которое гарантирует выполнение только одной операции в фиксированный интервал времени.
Еще одна концепция, связанная с регулированием функции, называется функцией анти-тряски, Этот брат также ждет временной интервал для выполнения операций, но после того, как он прерывается, ему необходимо перезапустить отсчет времени.
Это также является причиной использования регулирования функций в этом сценарии. В JavaScript регулирование функции может быть реализовано в виде setTimeout + closure:
function throttle (fn, interval = 500) {
let timer = null
let firstTime = true
return function (...args) {
if (firstTime) {
// 第一次加载
fn.apply(this, args)
return firstTime = false
}
if (timer) {
// 定时器正在执行中,跳过
return
}
timer = setTimeout(() => {
clearTimeout(timer)
timer = null
fn.apply(this, args)
}, interval)
}
}
В дополнение к реализации вышеописанного setTimeout + closure, его также можно реализовать через метод window.requestAnimationFrame(), который здесь повторяться не будет, желающие могут попробовать сами.
2. Метод getBoundingClientRect()
JavaScript предоставляет метод Element.getBoundingClientRect() для возврата размера элемента и информации о положении относительно области просмотра. Далее будут использоваться четыре свойства возвращаемого объекта:
- top и left — значения смещения между координатами левого верхнего угла целевого элемента и левого верхнего угла веб-страницы;
- ширина и высота — это ширина и высота самого целевого элемента.
Объедините высоту и ширину окна просмотра, чтобы определить, появляется ли элемент в области окна просмотра:
function isElementInViewport (el) {
const { top, height, left, width } = el.getBoundingClientRect()
const w = window.innerWidth || document.documentElement.clientWidth
const h = window.innerHeight || document.documentElement.clientHeight
return (
top <= h &&
(top + height) >= 0 &&
left <= w &&
(left + width) >= 0
)
}
3. Осознайте
Далее, в процессе реализации ленивой загрузки изображений, нужно обратить внимание на несколько мелких моментов:
- Событие прокрутки будет запущено только тогда, когда произойдет прокрутка.Здесь вам нужно вручную загрузить изображение первого экрана;
- При регистрации обработчика событий с помощью addEventListener необходимо сохранить ссылку на обработчик событий, чтобы зарегистрированный обработчик событий можно было уничтожить.
function LazyLoad (el, options) {
if (!(this instanceof LazyLoad)) {
return new LazyLoad(el)
}
this.setting = Object.assign({}, { src: 'data-src', srcset: 'data-srcset', selector: '.lazyload' }, options)
if (typeof el === 'string') {
el = document.querySelectorAll(el)
}
this.images = Array.from(el)
this.listener = this.loadImage()
this.listener()
this.initEvent()
}
LazyLoad.prototype = {
loadImage () {
return throttle(function () {
let startIndex = 0
while (startIndex < this.images.length) {
const image = this.images[startIndex]
if (isElementInViewport(image)) {
const src = image.getAttribute(this.setting.src)
const srcset = image.getAttribute(this.setting.srcset)
if (image.tagName.toLowerCase() === 'img') {
if (src) {
image.src = src
}
if (srcset) {
image.srcset = srcset
}
} else {
image.style.backgroundImage = `url(${src})`
}
this.images.splice(startIndex, 1)
continue
}
startIndex++
}
if (!this.images.length) {
this.destroy()
}
}).bind(this)
},
initEvent () {
window.addEventListener('scroll', this.listener, false)
},
destroy () {
window.removeEventListener('scroll', this.listener, false)
this.images = null
this.listener = null
}
}
В-четвертых, эта жизнь
Сегодня Интернет предоставляет разработчикам интерфейс IntersectionObserver, который может асинхронно отслеживать состояние пересечения целевого элемента и его предков или видовых экранов. Обратите внимание, что этот интерфейс является асинхронным. Он не запускается синхронно с прокруткой целевого элемента, поэтому он не повлияет на скорость прокрутки страницы.
Конструктор IntersectionObserver принимает два параметра, функцию обратного вызова и элемент конфигурации.
1. Элементы конфигурации
В пункте конфигурации есть три параметра:
- root: конкретный элемент-предок отслеживаемого объекта, по умолчанию — viewport ;
- rootMargin: при вычислении состояния пересечения присоединяйте границу к элементу-предку, тем самым эффективно расширяя или уменьшая область оценки элемента-предка;
- порог: Установите ряд порогов.Когда перекрестное состояние достигает порога, функция обратного вызова будет запущена.
2. Функция обратного вызова
Когда экземпляр IntersectionObserver выполняет функцию обратного вызова, он передает массив, содержащий объект IntersectionObserverEntry, который имеет в общей сложности семь свойств:
- time: возвращает метку времени от источника IntersectionObserver до времени, когда пересечение было запущено;
- цель: целевой элемент;
- rootBounds: информация о площади прямоугольника элементов-предков;
- boundingClientRect: Информация о прямоугольной области целевого элемента, которая согласуется с эффектом вышеупомянутого метода Element.getBoundingClientRect();
- пересечениеRect: информация об области пересечения между элементом-предком и целевым элементом;
- пересечениеRatio: возвращает соотношение пересеченияRect и boundingClientRect;
- isIntersecting: пересекается ли целевой элемент с элементом-предком.
3. Осознайте
Перед этим вам также необходимо понять методы экземпляра IntersectionObserver:
- наблюдать: начать слушать целевой элемент;
- ненаблюдать: перестать слушать определенный элемент;
- отключить: заставить объект IntersectionObserver прекратить прослушивание;
- takeRecords: возвращает массив объектов IntersectionObserverEntry для всех объектов прослушивания и прекращает прослушивание этих объектов.
Ниже приведен код реализации:
function LazyLoad (images, options = {}) {
if (!(this instanceof LazyLoad)) {
return new LazyLoad(images, options)
}
this.setting = Object.assign({}, { src: 'data-src', srcset: 'data-srcset', selector: '.lazyload' }, options)
this.images = images || document.querySelectorAll(this.setting.selector)
this.observer = null
this.init()
}
LazyLoad.prototype.init = function () {
let self = this
let observerConfig = {
root: null,
rootMargin: '0px',
threshold: [0]
}
this.observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
const target = entry.target
if (entry.intersectionRatio > 0) {
this.observer.unobserve(target)
const src = target.getAttribute(this.setting.src)
const srcset = target.getAttribute(this.setting.srcset)
if ('img' === target.tagName.toLowerCase()) {
if (src) {
target.src = src
}
if (srcset) {
target.srcset = srcset
}
} else {
target.style.backgroundImage = `url(${src})`
}
}
})
}, observerConfig)
this.images.forEach(image => this.observer.observe(image))
}
V. Резюме
На данный момент существует два основных способа отложенной загрузки изображений:
- Слушайте событие прокрутки и вычисляйте состояние пересечения между целевым элементом и областью просмотра с помощью getBoundingClientRect();
- Интерфейс IntersectionObserver.
Второй метод более беззаботный, и пока совместимость в порядке (описание этого предложения не подходит, если вы хотите счастливо использовать API, вы можете использовать метод полифиллаw3c IntersectionObserver polyfill, спасибо juejin Zhou Hui за исправление):
Для сравнения, первый метод должен оптимизировать событие прокрутки во всех аспектах, чтобы добиться эффекта «гладкой шелковистой» прокрутки страницы.
Напоследок две статьи по оптимизации прокрутки.