Скрипты темы почти всегда подключают с атрибутом 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 не приходит совсем, и без этой проверки страница, открытая в соседней вкладке, не запустила бы ни один скрипт, пока её не откроют.
Результат
| До | После | |
|---|---|---|
| оценка товарной | 92 | 95–98 |
| Speed Index | 4,7 с | 3,2 с (лучшие прогоны — 1,9 с) |
| первая отрисовка | 2531 мс | 1376 мс |
Порядок событий после правки — ровно такой, как нужно: разбор HTML закончен на 824 мс, кадр нарисован на 1132 мс, слайдер поднялся на 1184 мс. Посетитель видит страницу раньше, а слайдер «оживает» через 50 мс — разницы глазом не заметить.
Почему на тестовом сервере правка ничего не дала
Мы сначала проверили правку на тестовой копии — и ничего не увидели: там страница и так рисовалась за 300 мс с оценкой 96–100. Тест пустой, без трафика и тяжёлого контента, и главный поток там не успевал стать узким местом. На рабочем сайте с реальными данными картина была другой. Отсюда правило из четвёртой части: цифры для выводов — с боевого сайта.
А почему не перенести инициализацию на событие load?
Тогда интерфейс будет «мёртвым» дольше: слайдер, меню и формы заработают только после загрузки всех картинок. Пауза в один кадр отдаёт приоритет отрисовке и почти не задерживает интерактивность.
Слайдер первого экрана не будет выглядеть сломанным до инициализации?
Будет, если его вёрстка рассчитана только на состояние после запуска. Проверьте, как блок выглядит без скриптов: первый слайд должен быть виден в правильном размере. Иначе при инициализации вёрстка прыгнет — а это CLS, о нём в отдельной части цикла.
Страница долго белая, хотя всё загружено?
Пришлите адрес — посмотрю таймлайн и найду, что держит первую отрисовку.
// загружаю комментарии…