Почему рендеринг — узкое место, о котором все забывают
Вы оптимизировали изображения, настроили CDN, включили Brotli-сжатие и вырезали лишний JavaScript. Lighthouse показывает зелёные цифры, а страница всё равно подтормаживает при скролле и получает плохой INP. Знакомая ситуация?
Проблема в том, что большинство разработчиков зацикливаются на сетевых оптимизациях — уменьшают размер загружаемых ресурсов. Но при этом напрочь забывают про стоимость рендеринга: браузер должен построить Layout, вычислить стили и отрисовать Paint для каждого элемента на странице. И он делает это для всего контента — даже того, который пользователь, возможно, никогда не доскроллит.
Вот конкретный пример. На типичной странице e-commerce каталога с 50–100 карточками товаров браузер тратит сотни миллисекунд на рендеринг элементов, которые ещё даже не попали в viewport. Каждая карточка — это изображения, стили, расчёт позиции. Умножьте на сотню, и получите 300–800 мс рендеринга при первой загрузке, блокировку основного потока и проваленный порог INP в 200 мс.
Именно эту проблему решают CSS Containment и свойство content-visibility. По сути, это механизм ленивого рендеринга, встроенный прямо в браузер — буквально две-три строчки CSS, которые могут ускорить начальную отрисовку страницы в 7 раз. И в 2026 году это наконец работает во всех основных браузерах, без оговорок.
Что такое CSS Containment и зачем он нужен
CSS Containment — это спецификация, которая позволяет изолировать поддерево DOM от остальной страницы. Идея простая: если браузер знает, что конкретный блок не влияет на остальные элементы, он может оптимизировать рендеринг — пропустить пересчёт стилей и Layout для этого блока, когда меняется что-то снаружи.
Управляется это свойством contain, у которого есть несколько значений:
layout— изолирует расчёт Layout. Изменения внутри элемента не вызовут перерасчёт позиций снаружи, и наоборот.paint— ограничивает отрисовку границами элемента. Содержимое не может визуально «вылезти» за пределы контейнера.style— изолирует CSS-счётчики и другие стилевые эффекты. Не путайте с Shadow DOM — стили тут не скоупятся.size— размер элемента вычисляется без учёта дочерних элементов, как будто он пуст.content— сокращение дляlayout paint style. Самый безопасный вариант для широкого применения.
Базовый пример:
/* Изолируем секции страницы */
.page-section {
contain: content; /* layout + paint + style */
}
/* Для элементов с фиксированным размером можно добавить size */
.product-card {
contain: strict; /* layout + paint + style + size */
width: 300px;
height: 400px;
}
Значение contain: content — самый безопасный выбор. Оно говорит браузеру: «содержимое этого блока не влияет на внешний Layout и Paint». Браузер может пропустить пересчёт этого поддерева при изменениях в других частях страницы. А вот strict добавляет size containment и даёт максимальную оптимизацию, но требует явно заданных размеров — иначе элемент просто схлопнется в ноль (и это неприятный сюрприз, если вы к нему не готовы).
Когда contain работает, а когда — нет
Containment хорошо работает, когда на странице много независимых блоков одинаковой структуры: карточки товаров, посты в ленте, строки таблицы, комментарии. Каждый такой блок — отличный кандидат на изоляцию.
Но есть нюанс. contain не пропускает начальный рендеринг. Браузер всё равно вычисляет стили и Layout для всех элементов при первой загрузке — он просто делает это эффективнее при последующих обновлениях. Для настоящего ленивого рендеринга нужен следующий уровень — content-visibility.
content-visibility: auto — ленивый рендеринг на уровне браузера
Честно говоря, content-visibility — это именно то свойство, ради которого стоит читать эту статью. Оно идёт значительно дальше простого containment и позволяет браузеру полностью пропустить рендеринг элемента — включая Layout, Paint, вычисление стилей и даже hit-testing — до тех пор, пока элемент не приблизится к видимой области экрана.
У свойства три значения:
visible— значение по умолчанию, никаких оптимизаций.hidden— контент скрыт и не рендерится, но его состояние кешируется. При повторном показе рендеринг происходит мгновенно.auto— главная звезда. Контент за пределами viewport не рендерится, но при приближении к видимой области браузер автоматически запускает рендеринг just-in-time. При этом контент остаётся доступным для поиска по странице (Ctrl+F), навигации по Tab и скринридеров.
Минимальный рабочий пример
Допустим, у вас блог с длинным списком постов:
<!-- HTML: список постов блога -->
<main>
<article class="post">
<h2>Заголовок первого поста</h2>
<p>Контент...</p>
</article>
<article class="post">
<h2>Заголовок второго поста</h2>
<p>Контент...</p>
</article>
<!-- ...ещё 50 постов -->
</main>
/* CSS: две строчки — и рендеринг ускорен в разы */
.post {
content-visibility: auto;
contain-intrinsic-size: auto 500px;
}
Всё. Две строчки CSS. Серьёзно.
Браузер теперь рендерит только те посты, которые видны на экране (или близки к нему), а остальные полностью пропускает. При прокрутке контент появляется мгновенно — пользователь не замечает никакой задержки.
Как это работает внутри
Когда браузер встречает элемент с content-visibility: auto, происходит следующее:
- Элемент автоматически получает layout, style и paint containment.
- Если элемент за пределами viewport — добавляется size containment, и браузер полностью пропускает рендеринг содержимого.
- Элемент занимает пространство, заданное через
contain-intrinsic-size— это предотвращает скачки Layout. - Когда элемент приближается к viewport — size containment снимается, контент рендерится, и элемент принимает свой реальный размер.
- Когда элемент уходит обратно за viewport — процесс повторяется, но браузер запоминает реальный размер (если использовать
contain-intrinsic-size: auto <length>).
Критически важный момент: в отличие от display: none или visibility: hidden, контент с content-visibility: auto остаётся в DOM и accessibility tree. Пользователь может найти текст через Ctrl+F, перейти к элементу по Tab, а скринридер прочитает содержимое. Браузер просто не тратит ресурсы на визуальный рендеринг — и это красиво.
contain-intrinsic-size: борьба с прыгающим скроллбаром
Итак, вы добавили content-visibility: auto, запустили страницу... и скроллбар начал жить своей жизнью. Дёргается, прыгает, меняет длину на ходу. Это самая частая проблема при внедрении.
Причина простая: элементы с пропущенным рендерингом по умолчанию имеют нулевую высоту. Полоса прокрутки показывает неправильную длину страницы, а при скролле всё перескакивает, когда элементы получают реальную высоту.
Решение — свойство contain-intrinsic-size, которое задаёт «предполагаемый» размер элемента в нерендеренном состоянии. В 2026 году лучшая практика — обязательно использовать ключевое слово auto:
/* ❌ Старый подход: фиксированный placeholder */
.section {
content-visibility: auto;
contain-intrinsic-size: 0 500px; /* всегда 500px, даже если реальная высота другая */
}
/* ✅ Лучший подход 2026: auto + fallback */
.section {
content-visibility: auto;
contain-intrinsic-size: auto 500px; /* запоминает реальный размер, 500px — fallback */
}
/* ✅ Для grid/multi-column layout */
.grid-item {
content-visibility: auto;
contain-intrinsic-size: auto none; /* предотвращает overflow в grid */
}
Как работает auto <length>:
- При первом рендеринге элемент использует fallback-значение (500px в нашем примере).
- Когда элемент попадает в viewport и рендерится, браузер запоминает его реальный размер.
- Когда элемент уходит обратно за viewport, вместо fallback используется запомненный размер.
- Результат — скроллбар остаётся стабильным. Никаких дёрганий.
Небольшой совет: выбирайте fallback-значение максимально близко к средней высоте ваших элементов. Если посты блога обычно занимают 400–600px, ставьте auto 500px. Чем ближе fallback к реальности — тем стабильнее первоначальная прокрутка, пока браузер ещё не запомнил реальные размеры.
Реальные кейсы: цифры и результаты
Теория — это хорошо. Но давайте посмотрим, что дают эти оптимизации на реальных проектах.
Демо web.dev: ускорение рендеринга в 7 раз
В демонстрации команды Chrome на web.dev применение content-visibility: auto к блокам контента на длинной странице снизило время рендеринга с 232 мс до 30 мс — ускорение в 7,7 раз. И это на странице с относительно простым контентом. На реальных сайтах со сложной вёрсткой эффект бывает ещё заметнее.
Speed Kit: A/B тест на e-commerce (2024–2025)
Компания Speed Kit провела серию A/B тестов на живых e-commerce сайтах. На категорийных страницах крупного спортивного ритейлера с 36 SSR-рендеренными карточками товаров (каждая с глубиной DOM до 130 элементов) content-visibility: auto дал улучшение INP на 245 мс на 95-м перцентиле в мобильном Samsung Browser. По сути, одно CSS-свойство превратило «красный» INP в «зелёный».
Facebook: 250 мс экономии при навигации
Инженеры Facebook использовали content-visibility: hidden для кеширования неактивных вкладок в SPA. Результат — до 250 мс экономии при возврате к ранее посещённым представлениям. Браузер не перерисовывал всё заново, а просто «включал» уже отрендеренный контент.
Wantedly: ускорение Layout в 3–6 раз
На страницах с тысячами элементов в списках разработчики Wantedly получили ускорение Layout в 3–6 раз. Но тут они наступили на грабли: на адаптивных страницах высота элементов менялась в зависимости от устройства, и фиксированный contain-intrinsic-size приводил к постоянным пересчётам Layout. Переход на auto <length> решил проблему.
Практическое руководство: как внедрить content-visibility в проект
Шаг 1: определите кандидатов для оптимизации
Откройте Chrome DevTools → Performance, запишите трейс загрузки страницы и посмотрите на секцию Rendering. Ищите длительные задачи Layout и Paint. Лучшие кандидаты для content-visibility: auto:
- Секции страницы ниже первого экрана (below the fold)
- Списки карточек товаров, постов, комментариев
- Футер и боковые панели
- Табы и аккордеоны со скрытым контентом
- Длинные таблицы данных
Важно: не применяйте к элементам в первом viewport — это навредит LCP, потому что браузер задержит рендеринг критического контента. Это ровно противоположный эффект от желаемого.
Шаг 2: примените CSS
/* Базовый паттерн для секций страницы */
.below-fold-section {
content-visibility: auto;
contain-intrinsic-size: auto 600px;
}
/* Для карточек товаров в каталоге */
.product-card {
content-visibility: auto;
contain-intrinsic-size: auto 420px;
}
/* Для постов в ленте */
.feed-post {
content-visibility: auto;
contain-intrinsic-size: auto 350px;
}
/* Для футера */
footer {
content-visibility: auto;
contain-intrinsic-size: auto 300px;
}
Шаг 3: проверьте в DevTools
После применения content-visibility откройте Chrome DevTools → Performance и запишите ещё один трейс. Сравните время Rendering до и после — вы должны увидеть ощутимое сокращение времени Layout и Paint при начальной загрузке.
Ещё загляните во вкладку Elements — элементы с пропущенным рендерингом будут помечены специальным индикатором.
Шаг 4: проверьте edge cases
/* Если используете якорные ссылки — проверьте плавную прокрутку */
html {
scroll-behavior: smooth;
}
/* content-visibility: auto может сломать smooth scrolling к якорям внутри
нерендеренных секций. Решение — исключите целевые секции */
.section-with-anchor-target {
content-visibility: visible; /* не применяем ленивый рендеринг */
}
content-visibility: hidden — секретное оружие для SPA
Значение hidden часто упускают из виду, а зря — для одностраничных приложений оно невероятно полезно. В отличие от display: none, content-visibility: hidden сохраняет состояние рендеринга в кеше. При повторном показе элемент появляется мгновенно, без перерисовки с нуля.
/* SPA: переключение вкладок с кешированием */
.tab-panel {
content-visibility: hidden; /* скрыт, но кеширован */
}
.tab-panel.active {
content-visibility: visible; /* моментальное появление */
}
/* Анимация показа (поддержка с 2025+) */
.tab-panel {
content-visibility: hidden;
transition: content-visibility 300ms allow-discrete;
}
.tab-panel.active {
content-visibility: visible;
}
Практический сценарий: у вас дашборд с 5 вкладками, каждая набита сложными графиками и таблицами. Вместо того чтобы уничтожать и пересоздавать DOM при переключении (привет, React-ререндеры), вы просто переключаете content-visibility между hidden и visible. Переход — мгновенный.
Поддержка браузерами в 2026
С сентября 2025 года content-visibility получило статус Baseline Newly Available — поддержка во всех трёх основных движках:
- Chrome/Edge — полная поддержка с версии 85 (с 2020 года, кстати)
- Firefox — полная поддержка с версии 125 (2024)
- Safari — поддержка с версии 18 (2024)
Есть одна оговорка: в Safari (по состоянию на 18.3.1) встроенный поиск по странице (Cmd+F) не всегда находит текст внутри элементов с content-visibility: auto, если они за пределами viewport. Проблема известная, Apple работает над исправлением. В Chrome и Firefox поиск работает корректно.
Но вот что по-настоящему подкупает: content-visibility — это прогрессивное улучшение. Браузеры, которые не поддерживают свойство, просто его игнорируют. Страница работает как обычно, без поломок. Внедряйте прямо сейчас — никаких fallback не нужно.
Типичные ошибки и подводные камни
Ошибка 1: применение к элементам в первом viewport
Если вы примените content-visibility: auto к hero-секции или LCP-элементу, браузер может задержать его рендеринг. Это ухудшит LCP. Правило простое: применяйте только к контенту ниже первого экрана.
Ошибка 2: забыли contain-intrinsic-size
Без contain-intrinsic-size нерендеренные элементы имеют нулевую высоту. Это ломает скроллбар, вызывает CLS и создаёт совершенно ужасный UX при прокрутке. Всегда задавайте contain-intrinsic-size: auto <length>.
Ошибка 3: DOM API в скрытых элементах
Если JavaScript вызывает getBoundingClientRect(), offsetHeight или другие Layout API на элементе с пропущенным рендерингом — браузер будет вынужден отрендерить его синхронно. Весь выигрыш в производительности улетает. Проверьте свой JS-код на наличие таких вызовов — это одна из тех ошибок, которые сложно отловить без профилирования.
Ошибка 4: якорные ссылки и smooth scrolling
content-visibility: auto может сломать плавную прокрутку к якорям, если целевой элемент находится внутри нерендеренного блока. Браузер просто не знает точную позицию якоря, пока элемент не отрендерен. Решение — не применяйте content-visibility: auto к блокам с часто используемыми anchor-целями.
Ошибка 5: overflow и CSS-трансформации
Элементы с content-visibility: auto получают paint containment, что ведёт себя как overflow: hidden. Если дочерний элемент масштабируется через transform: scale() больше 1.0, он будет обрезан. Учитывайте это при работе с анимациями и hover-эффектами — я лично потратил на эту ошибку пару часов, прежде чем понял, в чём дело.
Комбинирование с другими техниками оптимизации
Максимальный эффект получается при комбинировании content-visibility с другими оптимизациями. Вот паттерн, который хорошо себя показывает:
/* Комплексная оптимизация длинной страницы */
/* 1. Hero-секция: максимальный приоритет */
.hero img {
fetchpriority: high;
loading: eager;
}
/* 2. Секции ниже fold: ленивый рендеринг */
.content-section:nth-child(n+2) {
content-visibility: auto;
contain-intrinsic-size: auto 500px;
}
/* 3. Изображения ниже fold: ленивая загрузка */
.content-section:nth-child(n+2) img {
loading: lazy;
decoding: async;
}
/* 4. Тяжёлые виджеты: комбинация contain и content-visibility */
.heavy-widget {
content-visibility: auto;
contain-intrinsic-size: auto 400px;
contain: layout style; /* дополнительная изоляция для обновлений */
}
Эта комбинация работает синергетически: content-visibility пропускает рендеринг скрытых секций, loading="lazy" откладывает загрузку изображений внутри них, а fetchpriority="high" обеспечивает приоритет LCP-элемента. В итоге — быстрый LCP, низкий INP и минимальное потребление ресурсов.
Измерение эффекта: как доказать результат
Внедряя content-visibility, обязательно замеряйте результат до и после. Без цифр вы не узнаете, насколько это помогло конкретно вашему проекту. Вот практический чеклист:
- Chrome DevTools → Performance: запишите трейс и сравните суммарное время Rendering (Layout + Paint) до и после.
- Web Vitals Extension: проверьте INP при типичных взаимодействиях — клик, скролл, ввод текста.
- Lighthouse: сравните общий Performance Score и конкретные метрики.
- CrUX (Chrome UX Report): через 28 дней проверьте полевые данные для INP и CLS.
- CLS: убедитесь, что
contain-intrinsic-sizeне вносит дополнительный Layout Shift.
Полезный трюк: в Chrome DevTools → Rendering включите опцию «Paint flashing». Она подсвечивает зелёным области, которые перерисовываются. С content-visibility: auto вы увидите, что элементы за viewport не мигают зелёным — они действительно не рендерятся. Выглядит весьма убедительно, особенно когда нужно показать результат тимлиду или менеджеру.
Часто задаваемые вопросы
Влияет ли content-visibility на SEO и индексацию?
Нет. Контент с content-visibility: auto остаётся в DOM и полностью доступен поисковым роботам. В отличие от display: none, этот контент не скрыт — он просто не отрисован визуально. Googlebot видит весь контент независимо от CSS-рендеринга. Более того, улучшение Core Web Vitals (INP, CLS) за счёт этого свойства может положительно повлиять на ранжирование.
Можно ли использовать content-visibility с фреймворками (React, Vue, Angular)?
Да, без проблем. content-visibility — чистое CSS-свойство, которое работает независимо от фреймворка. Добавьте его в стили компонентов-контейнеров: списков, секций, карточек. Единственное — проверьте, что ваш JS-код не вызывает Layout API (offsetHeight, getBoundingClientRect()) на нерендеренных элементах, иначе браузер принудительно запустит рендеринг и выигрыш будет потерян.
Чем content-visibility: hidden отличается от display: none?
Главное отличие — content-visibility: hidden кеширует состояние рендеринга. При повторном показе элемент появляется мгновенно, без полного перерендеринга. С display: none элемент полностью удаляется из рендер-дерева, и при повторном показе браузер строит Layout и Paint заново. Для SPA с переключением вкладок разница очень заметна.
Безопасно ли использовать content-visibility в продакшене?
Вполне. С сентября 2025 года свойство имеет статус Baseline Newly Available и поддерживается в Chrome, Firefox и Safari. Даже если какой-то экзотический браузер его не поддержит — свойство просто игнорируется, страница работает нормально. Это полностью безопасное прогрессивное улучшение, которое можно катить в прод без страха.
Насколько content-visibility улучшает INP?
Зависит от структуры страницы. На страницах с большим количеством контента ниже fold (каталоги, ленты, длинные статьи) реальные улучшения INP составляют 100–250 мс — этого часто достаточно, чтобы перейти из «красной» зоны (>200 мс) в «зелёную». Наибольший эффект — на мобильных устройствах, где процессор слабее и каждая миллисекунда рендеринга на счету.