Картинки лениво загрузились и наступили на яму

внешний интерфейс GitHub

Оригинальный адрес:Картинки лениво загрузились и наступили на яму

принцип

Наибольшее влияние на скорость загрузки веб-страницы оказывает изображение. Обычное изображение может иметь размер в несколько мегабайт. Когда изображений много, скорость загрузки веб-страницы становится очень низкой.

Чтобы оптимизировать производительность веб-страницы и взаимодействие с пользователем, мы懒加载.

Ленивая загрузка — это способ оптимизации производительности веб-страниц, принцип которого заключается в предпочтительной загрузке изображений в видимой области вместо загрузки всех изображений сразу. Когда браузер прокручивается и изображение попадает в область просмотра, оно загружается. установив картинкуsrcатрибут, чтобы позволить браузеру инициировать запрос изображения. Когда это свойство пусто или отсутствует, запрос не будет отправлен.

выполнить

Ленивая загрузка, описанная в этой статье, — это загрузка прокрутки в вертикальном направлении, а горизонтальная прокрутка пока не рассматривается.

Реализация ленивой загрузки в основном предназначена для определения основной логики того, достигло ли текущее изображение видимой области. Сначала разберемся с идеями реализации:

  1. получить все фотографииimg dom.
  2. Пройдитесь по каждому изображению, чтобы определить, находится ли текущее изображение в видимой области.
  3. Если это так, установите атрибут src изображения.
  4. привязать окноscrollсобытие и отслеживать его для событий.

HTML-структура

<div class="container">
    <div class="img-area">
        <img id="first" data-src="./img/ceng.png" alt="">
    </div>
    <div class="img-area">
        <img data-src="./img/data.png" alt="">
    </div>
    <div class="img-area">
        <img data-src="./img/huaji.png" alt="">
    </div>
    <div class="img-area">
        <img data-src="./img/liqi1.png" alt="">
    </div>
    <div class="img-area">
        <img data-src="./img/liqi2.png" alt="">
    </div>
    <div class="img-area">
        <img data-src="./img/steve-jobs.jpg" alt="">
    </div>
</div>

В настоящее время тег img не имеет атрибута src, мы помещаем реальный адрес изображения в атрибут, здесь мы используем HTML5.dataатрибут, поместите реальный адрес в пользовательскийdata-srcсередина.

Определить, попало ли изображение в видимую область

Есть два подхода к этой логике, позвольте мне объяснить.

метод первый

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

Метод расчета высоты видимой области относительно верха документа:

const clientHeight = document.documentElement.clientHeight; // 视口高度,也就是窗口的高度。
const scrollHeight = document.documentElement.scrollTop + clientHeight; // 滚动条偏移文档顶部的高度(也就是文档从顶部开始到可视区域被抹去的高度) + 视口高度

Нарисуйте картинку для понимания:

Затем следует вычислить высоту изображения от верхней части документа. Есть два способа, первый способ по элементуoffsetTopсвойства для расчета. Из приведенного выше рисунка мы знаем, что свойство offsetTop элемента относится кpositionдля非 staticЭлемент-предок , то естьchild.offsetParent. В то же время необходимо преобразовать элемент-предокborderС учетом, проходимchild.offsetParent.clientTopВы можете получить толщину границы.

Отсюда получаем метод расчета высоты элемента от верха документа:

function getTop(el, initVal) {
    let top = el.offsetTop + initVal;
    if (el.offsetParent !== null) {
        top += el.offsetParent.clientTop;
        return getTop(el.offsetParent, top);
    } else {
        return top;
    }
}

Этот метод здесь использует尾递归调用. Может улучшить производительность рекурсии. Конечно, это также можно сделать с помощью цикла:

function getTop(el) {
    let top = el.offsetTop;
    var parent = el.offsetParent;
    while(parent !== null) {
        top += parent.offsetTop + parent.clientTop;
        parent = parent.offsetParent;
    }
    return top;
}

Второй метод заключается в использованииelement.getBoundingClientRect()API получает максимальное значение напрямую.

Возвращаемое значение getBoundingClientRect выглядит следующим образом:

var first  = document.getElementById('first');
getTop(first, 0);  // 130
console.log(first.getBoundingClientRect().top); // 130

Таким образом, мы получаем метод суждения:

function inSight(el) {
    const clientHeight = document.documentElement.clientHeight;
    const scrollHeight = document.documentElement.scrollTop + clientHeight;
    // 方法一
    return getTop(el, 0) < scrollHeight;
    // 方法二
    // return el.getBoundingClientRect().top < clientHeight;
}

Следующим шагом является оценка и назначение каждого изображения.

function loadImg(el) {
    if (!el.src) {
        el.src = el.dataset.src;
    }
}

function checkImgs() {
    const imgs = document.getElementsByTagName('img');
    Array.from(imgs).forEach(el => {
        if (inSight(el)) {
            loadImg(el);
        }
    })
    console.log(count++);
}

Наконец привязать к окнуonscrollсобытия иonloadмероприятие:

window.addEventListener('scroll', checkImgs, false);
window.onload = checkImgs;

мы знаем что-то вродеscrollилиresizeТакой обозреватель событий может срабатывать много раз за короткий промежуток времени, чтобы повысить производительность веб-страницы, нам нужендросселированиедля управления многократным запуском функции и выполнения обратного вызова только один раз в течение определенного периода времени (например, 500 мс).

/**
 * 持续触发事件,每隔一段时间,只执行一次事件。
 * @param fun 要执行的函数
 * @param delay 延迟时间
 * @param time 在 time 时间内必须执行一次
 */
function throttle(fun, delay, time) {
    var timeout;
    var previous = +new Date();
    return function () {
        var now = +new Date();
        var context = this;
        var args = arguments;
        clearTimeout(timeout);
        if (now - previous >= time) {
            fun.apply(context, args);
            previous = now;
        } else {
            timeout = setTimeout(function () {
                fun.apply(context, args);
            }, delay);
        }
    }
}
window.addEventListener('scroll', throttle(checkImgs, 200, 1000), false);

Способ второй

HTML5 имеет новыйIntersectionObserverAPI, который может автоматически отслеживать видимость элемента.

Основное использование:

var observer = new IntersectionObserver(callback, option);

// 开始观察
observer.observe(document.getElementById('first'));

// 停止观察
observer.unobserve(document.getElementById('first'));

// 关闭观察器
observer.disconnect();

Обратный вызов наблюдателя вызывается при изменении видимости цели.

function callback(changes: IntersectionObserverEntry[]) {
    console.log(changes[0])
}

// IntersectionObserverEntry
{
    time: 29.499999713152647,
    intersectionRatio: 1,
    boundingClientRect: DOMRectReadOnly {
        bottom: 144,
        height: 4,
        left: 289,
        right: 293,
        top: 140,
        width: 4,
        x: 289,
        y: 140
    },
    intersectionRect: DOMRectReadOnly,
    isIntersecting: true,
    rootBounds: DOMRectReadOnly,
    target: img#first
}

Детальное объяснение:

  • время: время изменения видимости, которое представляет собой высокоточную метку времени в миллисекундах.
  • пересечениеRatio: видимое отношение целевого элемента, то есть отношение пересеченияRect к boundingClientRect, 1, когда он полностью виден, и меньше или равно 0, когда он полностью невидим
  • boundingClientRect: информация о прямоугольной области целевого элемента
  • пересечениеRect: Информация о области пересечения целевого элемента и области просмотра (или корневого элемента)
  • rootBounds: информация о прямоугольной области корневого элемента, возвращаемое значение метода getBoundingClientRect() или null, если корневого элемента нет (то есть прокрутка непосредственно относительно окна просмотра)
  • isIntersecting: введено ли окно просмотра, логическое значение
  • цель: целевой элемент, за которым нужно наблюдать, является объектом узла DOM.

Используйте IntersectionObserver для реализации ленивой загрузки изображения:

function query(tag) {
    return Array.from(document.getElementsByTagName(tag));
}
var observer = new IntersectionObserver(
    (changes) => {
        changes.forEach((change) => {
            if (change.intersectionRatio > 0) {
                var img = change.target;
                img.src = img.dataset.src;
                observer.unobserve(img);
            }
        })
    }
)
query('img').forEach((item) => {
    observer.observe(item);
})

Посмотреть полный кодgithub

над :)