Скрипты темы почти всегда подключают с атрибутом defer — и считают, что так они не мешают отрисовке. Это полуправда. defer значит «не блокировать разбор HTML», но выполняются такие скрипты сразу после разбора, до первой отрисовки. Пока строятся слайдеры, читается геометрия блоков и поднимаются компоненты, экран остаётся пустым — при уже загруженных стилях и картинках.

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

Как это выглядело

Товарная страница рабочего сайта, вес в окне первой отрисовки уже убран (восьмая часть), а оценка застряла на 92. Таймлайн показал: CSS готов, картинка первого экрана загружена, а экран белый 2,5 секунды. Всё это время главный поток занят инициализацией: слайдер галереи считает размеры, компоненты подписываются на события, меряют блоки.

Модель PageSpeed видит это как «страница долго не рисуется» — и, по правилу из первой части, всё, что успело запроситься за эти секунды, попадает в окно.

Лечение: одна строка перед инициализацией

Перед запуском компонентов нужно дать браузеру нарисовать кадр. Для этого есть простой приём: requestAnimationFrame срабатывает прямо перед отрисовкой кадра, а setTimeout внутри него — уже после.

// nextPaint.js — дождаться, пока браузер нарисует текущий кадр
export default function nextPaint(limit = 300) {
  return new Promise(resolve => {
    // в фоновой вкладке requestAnimationFrame не приходит вовсе — не ждём
    if (document.visibilityState === "hidden") return resolve();
    const cap = setTimeout(resolve, limit); // потолок: ничего не должно зависнуть
    requestAnimationFrame(() => {
      // rAF срабатывает ДО кадра, setTimeout внутри него — уже ПОСЛЕ
      setTimeout(() => { clearTimeout(cap); resolve(); }, 0);
    });
  });
}

// точка входа скриптов страницы
await domReady();
await nextPaint();      // сначала кадр — потом слайдеры, замеры и прочая инициализация
initComponents();

Два обязательных условия. Потолок по времени (у нас 300 мс) и мгновенный выход в фоновой вкладке. В скрытой вкладке requestAnimationFrame не приходит совсем, и без этой проверки страница, открытая в соседней вкладке, не запустила бы ни один скрипт, пока её не откроют.

Результат

ДоПосле
оценка товарной9295–98
Speed Index4,7 с3,2 с (лучшие прогоны — 1,9 с)
первая отрисовка2531 мс1376 мс
товарная страница рабочего сайта, серии прогонов

Порядок событий после правки — ровно такой, как нужно: разбор HTML закончен на 824 мс, кадр нарисован на 1132 мс, слайдер поднялся на 1184 мс. Посетитель видит страницу раньше, а слайдер «оживает» через 50 мс — разницы глазом не заметить.

Почему на тестовом сервере правка ничего не дала

Мы сначала проверили правку на тестовой копии — и ничего не увидели: там страница и так рисовалась за 300 мс с оценкой 96–100. Тест пустой, без трафика и тяжёлого контента, и главный поток там не успевал стать узким местом. На рабочем сайте с реальными данными картина была другой. Отсюда правило из четвёртой части: цифры для выводов — с боевого сайта.

А почему не перенести инициализацию на событие load?

Тогда интерфейс будет «мёртвым» дольше: слайдер, меню и формы заработают только после загрузки всех картинок. Пауза в один кадр отдаёт приоритет отрисовке и почти не задерживает интерактивность.

Слайдер первого экрана не будет выглядеть сломанным до инициализации?

Будет, если его вёрстка рассчитана только на состояние после запуска. Проверьте, как блок выглядит без скриптов: первый слайд должен быть виден в правильном размере. Иначе при инициализации вёрстка прыгнет — а это CLS, о нём в отдельной части цикла.

Страница долго белая, хотя всё загружено?

Пришлите адрес — посмотрю таймлайн и найду, что держит первую отрисовку.

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

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

Комментарии

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

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