loading="lazy" кажется универсальным ответом: поставил атрибут — картинка не грузится, пока не понадобится. На деле браузер решает «понадобится или нет» по расстоянию до экрана, и у этого решения есть слепое пятно: элементы, скрытые через display: none.

Это двадцать вторая часть цикла «PageSpeed без мифов».

Что происходит

У скрытого через display: none элемента нет положения на странице. Браузер не может вычислить, далеко ли он от экрана, — и в таких случаях Chrome загружает ленивую картинку сразу, как обычную. Где это встречается:

  • мобильное меню с картинками или логотипами — скрыто, пока не нажали бургер;
  • вкладки — все, кроме активной;
  • фильтры и аккордеоны в свёрнутом состоянии;
  • десктопные блоки, спрятанные на телефоне (и наоборот).

На одном из сайтов картинка в скрытом мобильном меню добавляла 27 КБ в окно первой отрисовки на каждой странице. Немного — но это вес, который не нужен ни одному посетителю, пока он не открыл меню.

Отдельный случай: декор, которого никто не видит

Ещё хуже — картинки, которые скрыты навсегда. При редизайне блок убирают через display: none «на время», а файл остаётся в разметке и грузится у всех. Так у нас нашёлся декоративный PNG с дымом на 94 КБ, который не показывался ни на одной ширине экрана.

Лечение

  1. Мёртвое — удалить. Если картинка не показывается ни при каком состоянии страницы, её место не в CSS, а в корзине.
  2. Скрытое до действия — отложить адрес. Вместо src и srcset положить адреса в data-src и data-srcset и подставить их, когда элемент раскрывают.
  3. Порядок подстановки: сначала <source> внутри <picture>, потом <img>. Иначе браузер успеет начать загрузку запасного формата из img до того, как узнает про avif и webp.
// при раскрытии меню / вкладки / фильтра — подставить отложенные адреса
function revealImages(container) {
  container.querySelectorAll("source[data-srcset]").forEach(s => {
    s.srcset = s.dataset.srcset;           // сначала <source>…
    s.removeAttribute("data-srcset");
  });
  container.querySelectorAll("img[data-src]").forEach(img => {
    if (img.dataset.srcset) img.srcset = img.dataset.srcset;
    img.src = img.dataset.src;             // …потом <img>
    img.removeAttribute("data-src");
  });
}
menuButton.addEventListener("click", () => revealImages(mobileMenu), { once: true });

Для вкладок и слайдеров удобно подставлять адреса ещё и заранее — при наведении на вкладку или переключении слайда, — чтобы картинка была готова к моменту показа.

Как найти у себя

В консоли браузера — какие картинки загружены, но находятся внутри невидимых элементов:

[...document.images]
  .filter(i => i.complete && i.naturalWidth && !i.offsetParent)
  .map(i => i.currentSrc.split("/").pop())

offsetParent равен null у элементов внутри display: none. Всё, что попало в список, загружено зря — по крайней мере до первого действия посетителя.

Подозреваете лишние картинки в окне отрисовки?

Пришлите адрес — найду скрытые и мёртвые картинки на ключевых страницах.

· полезно?
АЧ
Алексей Чикун веб-разработчик, команда TRIOHEAD

Делаю сайты и сервисы целиком: от вёрстки до сервера. Пишу о том, что проверил на своих проектах.

Комментарии

// загружаю комментарии…

Комментарии публикуются после проверки. Ссылки и HTML не принимаются.