Главная / Статьи / Игнорирование отрисовки вне экрана с использованием content-visibility и contain-intrinsic-size

Игнорирование отрисовки вне экрана с использованием content-visibility и contain-intrinsic-size

Узнайте, как функция видимости контента автоматически снижает затраты на форматирование длинных страниц, генерируемых сервером, почему обязательно использовать параметр contain-intrinsic-size, и как это сравнивается с виртуализацией.

2789 слов

Длинные страницы, заполненные повторяющимися элементами — такими как списки товаров, ленты новостей, архивы и большие таблицы, — часто работают медленно даже тогда, когда объем JavaScript-кода минимален. Причина обычно заключается в том, что браузер вычисляет стили и макет для каждого элемента на странице, включая сотни карточек, до которых посетитель еще не прокрутился и, возможно, никогда не доберется. В этом руководстве показано, как два свойства CSS — content-visibility и contain-intrinsic-size — позволяют браузеру отложить выполнение этих операций, как это связано с показателями Core Web Vitals и процессом индексации, а также где эта техника не сработает или будет неподходящим инструментом.

Типичный случай: длинный список, работающий медленно без очевидных причин

Представьте страницу категории «Посмотреть все товары» в интернет-магазине. Примерно 600 карточек товаров, каждая из которых содержит изображение, название, цену и оценку, генерируются на сервере в одну длинную страницу. Здесь нет пагинации, ни функции бесконечного прокручивания, ни виртуализации. Компания предпочитает такой формат: один URL, возможность просмотра каждого товара, доступ к всему с помощью функции поиска в странице браузера.

На ноутбуке среднего класса для того, чтобы страница стала интерактивной, требуется почти четыре секунды. Самым вероятным виновником считается JavaScript: слишком большой пакет кода, некорректно работающий метод useEffect, постоянная перерисовка компонентов в цикле. Тщательный анализ пакета кода не выявил ничего, что могло бы объяснить задержку.

Панель производительности в DevTools рассказывает другую историю. Большая часть времени основного потока тратится на операции Layout, и это происходит ещё до того, как скрипты успевают повлиять на работу страницы.

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

Отрисовка — это не одна операция. Сначала браузер определяет стили для каждого элемента, затем выполняет расчёты макета, чтобы установить размер и положение каждого блока, и только после этого рисует пиксели. Рисование в основном ограничивается тем, что находится внутри или рядом с областью отображения, но процессы определения стилей и расчёта макета выполняются для всего документа, включая контент, находящийся глубоко внизу страницы. К тому моменту, когда браузер решает, что нужно отрисовать, дорогостоящие геометрические вычисления уже завершены.

В примере магазина это означает, что все 600 карточек, каждая из которых содержит поле для изображения, заголовок упаковки, цену и строку с оценкой, проходят через процессы стилизации и форматирования ещё до того, как покупатель увидит первую из них. Покупатель видит, возможно, восемь товаров. Остальные 592 пока бесполезны, но браузер не может знать, прокрутит ли посетитель страницу вниз, поэтому по умолчанию он считает их всеми содержимым, которое должно быть готово сразу. Именно этот ресурсозатратный подход и следует устранить. Если вам нужно вспомнить, чем отличаются по стоимости эти этапы обработки, ознакомьтесь с нашим разбором того, сколько стоит для браузера процесс рифлоу, перерисовки и композиции.

Решение: две декларации для повторяющегося элемента

Примените оба свойства к элементу, который повторяется, а именно к карточке товара. Первое свойство указывает браузеру, что он может пропустить отрисовку содержимого карточки, если она находится далеко от области просмотра. Второе свойство задаёт размер заменителя для карточек, размер которых пока неизвестен.

.product-card {
  content-visibility: auto;
  contain-intrinsic-size: auto 340px;
}

При использовании content-visibility: auto карточка, находящаяся далеко от области просмотра, остаётся в DOM и в дереве доступности, и функция find-in-page всё равно находит её текст. Изменяется лишь то, что браузер не тратит ресурсы на стилизацию, форматирование и отрисовку её содержимого до тех пор, пока карточка не приблизится к экрану. Фактически это свойство применяет ограничения на форматирование, стили и отрисовку к элементу, что позволяет браузеру рассматривать его поддерево как независимое и безопасно его пропускать.

Насколько велики могут быть преимущества

В демонстрации, опубликованной на web.dev, Google взял длинную страницу, разделенную на секции, и сократил время ее отрисовки с 232 мс до 30 мс, что соответствует улучшению примерно в семь раз. Следует помнить, что это специально созданная демонстрационная страница, а не производственный сайт. Для реальной страницы с описанным выше содержимым более реалистичным является улучшение примерно в два раза, что уже может стать разницей между страницей, которая продолжает загружаться, и страницей, готовой к использованию сразу после отрисовки.

Экстремальные примеры показывают предел возможностей. В выступлении на Chrome Dev Summit этот подход был применен к огромной одностраничной HTML-спецификации с более чем 270 000 узлами DOM, и время формирования макета сократилось с примерно 50 секунд до около 400 миллисекунд. Таких страниц мало, но они иллюстрируют, сколько усилий вкладывается в содержимое, которое никто не может увидеть.

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

Почему свойство contain-intrinsic-size не является факультативным

Если использовать только content-visibility: auto, ползунок прокрутки начинает скачками перемещаться во время прокрутки, что легко можно спутать с независимой ошибкой.

Причина проста. У карточки, отрисовка которой была пропущена, нет известной высоты, поэтому она задаётся как пустая. При наличии сотен пропущенных карточек общая высота документа сильно искажается, и ползунок отражает эту неверную высоту. Когда карточки появляются в поле зрения и получают свои реальные размеры, документ увеличивается в размерах, и ползунок смещается.

contain-intrinsic-size указывает размер, который следует предполагать при пропуске элемента. Значение 340px в примере — это оценка для карточек, которые ещё не отрисованы. Точность не требуется; достаточно разумной приблизительной цифры, чтобы скроллбар не резко двигался.

Что добавляет ключевое слово auto

Ключевое слово auto легко упустить из виду, но оно имеет огромное значение. При использовании auto браузер после отрисовки карточки фиксирует её реальный размер. Если позже карточка выходит за пределы области отображения и снова пропускается, браузер использует этот сохранённый размер вместо вашей оценки. Без auto каждая карточка снова использует фиксированную оценку каждый раз, когда выходит за пределы обзора.

Картинки на экране всегда отображаются с их истинной высотой в любом случае. Разница проявляется при использовании контента с переменной высотой: длинного названия товара, которое переносится на новую строку, или значка скидки, добавляющего ещё одну строку. Без параметра auto картинки возвращаются к предполагаемым размерам, когда покидают область отображения и меняется положение прокрутки. Всегда используйте auto.

Поддержка браузерами и постепенное улучшение

Поддержка этой функции больше не ограничивается только Chrome. Chrome поддерживает content-visibility с версии 85 (2020 г.), Firefox — с версии 125, а Safari — с версии 18. На момент написания статус этой функции — Baseline Newly Available, что было достигнуто 15 сентября 2025 года, что означает её работу во всех трех основных браузерах; проверьте актуальные данные о совместимости, если вы поддерживаете более старые версии.

Браузеры, которые не понимают этого свойства, просто игнорируют его и отображают всё так же, как обычно. Это делает его прогрессивным улучшением, не создающим никаких проблем для старых клиентов.

Как это связано с Core Web Vitals и SEO

Мотивацией здесь является производительность, но страницы категорий — именно тот тип URL-адресов, за которыми внимательно следят команды по поиску. Они являются публичными, ориентированы на ценные запросы, и Google измеряет их производительность для реальных пользователей.

Работа с макетом и отрисовкой влияет на два из трех показателей Core Web Vitals, которые Google использует в качестве сигналов для ранжирования:

  • Interaction to Next Paint (INP) напрямую выигрывает от этого. Игнорирование процесса отрисовки карточек, находящихся вне экрана, освобождает главный поток, поэтому нажатия и клики получают ответ быстрее.
  • Largest Contentful Paint (LCP) оказывает влияние более косвенным образом. На длинных страницах меньшее количество элементов интерфейса до первого отрисовывания контента обычно позволяет крупному видимому содержимому появиться раньше.
  • Множество некоммерческих страниц имеют такую структуру: от документации и новостных лент до архивов блог-постов, длинных тредов обсуждений и статей с несколькими разделами. Везде, где множество похожих элементов расположены вертикально, а большинство из них находится вне экрана, и страница является публичной, эти изменения влияют на показатели, которые измеряет Google.

    Будьте осторожны с обещаниями улучшения ранжирования. Отслеживание одного сайта в течение нескольких недель показывает очень мало, а ранжирование зависит от гораздо большего количества показателей. То, чего можно разумно ожидать, — это движение в правильном направлении по данным из инструментов вроде PageSpeed Insights, когда накопится достаточное количество примеров реальных пользователей.

    Положительные эффекты не ограничиваются только публичными страницами. Панели управления, таблицы администратора и внутренние инструменты также сталкиваются с теми же затратами на отрисовку 600 строк. За процедурой входа нет преимуществ в плане поиска, но улучшение пользовательского опыта остается тем же благодаря использованию одних и тех же CSS-правил.

    Скрывает ли пропуск отрисовки контент от поисковых роботов?

    Это первый вопрос, который задаст внимательный специалист по SEO, и он вполне обоснован учитывая множество схем ленивой загрузки, делающих контент невидимым для ботов. Ключевое отличие заключается в том, что content-visibility: auto является оптимизацией процесса отрисовки, а не изменением видимости. Контент существует в HTML и DOM с момента загрузки страницы; только его разметка и отрисовка откладываются до тех пор, пока элемент не приблизится к области отображения.

    Googlebot не прокручивает страницу так, как это делает человек. Вместо этого он использует крайне большую область отображения для отрисовки, а затем анализирует полученную DOM-структуру. Карты, которые пропускаются, но всё же существуют, являются частью этой DOM-структуры, поэтому каждый заголовок товара и цена индексируются так же, как и любой другой контент.

    В отличие от этого, раньше контент вставлялся только при срабатывании события прокрутки. Роботы-поисковики не генерируют таких событий, поэтому такой контент действительно мог отсутствовать в индексе. Параметр content-visibility не может вызвать эту проблему, поскольку ничего не вставляется позже — всё присутствует с самого начала.

    Одно ограничение не связано с самим объектом. Если страница формирует свой контент с помощью JavaScript на стороне клиента, возникает отдельная проблема с SEO. Googlebot действительно выполняет JavaScript, но отрисовка происходит по очереди, что замедляет процесс и снижает надежность; к тому же многие другие роботы плохо справляются с JavaScript. Для общедоступного контента необходимо отрисовывать HTML на сервере и дополнительно добавить атрибут content-visibility в CSS. Такое сочетание обеспечивает возможность индексации контента и улучшает его показатели.

    Сравнение с бесконечным прокручиванием, виртуализацией и пользовательскими обозревателями

    Бесконечное прокручивание, API с пагинацией и виртуализация направлены на решение одной и той же проблемы медленных длинных страниц, поэтому стоит честно их сравнить.

    Загрузка при прокручивании и API с пагинацией

    Загрузка дополнительных элементов по мере прокрутки решает проблему на уровне данных. Это правильный выбор, когда набор данных действительно огромен и не должен передаваться в браузер полностью.

    У этого есть свои издержки. Требуются изменения в API, обработка состояний загрузки, слушатели прокрутки и отслеживание состояния уже загруженных данных. Также меняется пользовательский опыт: функция поиска внутри страницы не может найти элементы, которые ещё не загружены, а добраться до конца списка становится утомительно. На странице публичной категории добавляется дополнительная нагрузка на SEO, поскольку товары, которые появляются только после прокрутки, недоступны для поисковых роботов; необходимы URL с пагинацией и дополнительная маркировка, чтобы они оставались индексируемыми. Для 600 карточек, уже находящихся в HTML, это означает переписывание кода плюс дополнительную работу по SEO для решения проблемы, которая на самом деле связана с отображением контента.

    Библиотеки виртуализации

    Виртуализация с использованием таких библиотек, как react-window или TanStack Virtual, хранит всю базу данных в памяти, удаляя элементы DOM того, что находится за пределами видимого окна. Этот подход работает, и при очень больших объемах данных он является лучшим выбором, как будет рассказано ниже.

    Цена связана с зависимостями JavaScript, необходимостью переписывания компонентов и крайне сложным управлением строками разной высоты. Поскольку удаленные элементы вообще отсутствуют в DOM, инструменты поиска на странице не могут их обнаружить, скрин-ридеры испытывают с ними трудности, а поисковые роботы на публичных страницах их пропускают.

    Подход с ручным использованием IntersectionObserver

    Воспроизведение элементов вручную при сигнале от IntersectionObserver о их видимости означает повторное выполнение в основном потоке JavaScript того, что уже делает параметр content-visibility: auto, а также возвращение ошибок, связанных с фиксацией положения при прокрутке, которые браузер уже решил встроенно. Сегодня нет оснований писать код таким образом.

    Выбор между ними

    Настоящий аргумент в пользу content-visibility заключается не в том, что он превосходит эти методы, а в том, что он гораздо дешевле. Это просто свойство CSS: никакого JavaScript, никаких изменений в API, никакой переписки кода, и контент остается в DOM для поиска, работы средств помощи и функций поиска внутри страницы.

    Необходимо четко указать компромисс. content-visibility экономит ресурсы на отрисовке, но не память. Каждый узел DOM по-прежнему существует. При 600 карточках это незначительно. Но при 50 000 или 100 000 элементах размер DOM сам по себе становится проблемой, и тогда виртуализация оправдывает свою сложность. Определите, с какой из двух проблем у вас дело — затраты на отрисовку или размер DOM, прежде чем выбирать инструмент.

    Где он работает, а где тихо ломает всё

    Это не свойство, которое стоит применять везде. Оно наиболее эффективно там, где одна и та же структура повторяется многократно, например:

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

    Измерение пропущенного контента дает неверные значения

    Это свойство вступает в конфликт с кодом, который требует точных геометрических данных от поддерева до того, как это поддерево будет отрендерено. Типичным примером является получение высоты внутреннего элемента строки, который все еще находится вне экрана.

    const height = row
      .querySelector('.details')
      .getBoundingClientRect()
      .height;
    

    Измерение внутреннего содержимого пропущенного элемента с помощью getBoundingClientRect() до того, как он был отрендерен, приводит к нулевым или иным некорректным значениям. Собственные параметры элемента отражают размеры места замещения, указанные в свойстве contain-intrinsic-size, которые также могут не соответствовать действительности. Подобный код часто встречается в реальных интерфейсах, например, когда вы:

    • размещаете подсказку рядом с триггером
    • определяете начальные и конечные значения для анимации
    • решаете, где откроется выпадающий меню
    • устанавливаете размеры строк в виртуальном списке
    • реализуете логику фиксированного позиционирования
    • выравниваете один компонент относительно другого

    Если точные геометрические параметры важны до того, как контент станет видимым, необходимо тщательно протестировать код перед добавлением этого свойства.

    Другие неподходящие варианты

    • Закреплённые заголовки, а также макеты, в которых расчёты работают только тогда, когда у всех дочерних элементов одновременно имеется реальная геометрия.
    • Любой контент, находящийся выше видимой области. Этот контент должен отрисовываться немедленно в любом случае, поэтому это свойство не приносит преимуществ и требует дополнительной обработки.
    • Элементы, у которых визуальные эффекты выходят за пределы их контейнера. Благодаря свойству ограничения отрисовки содержимое, выходящее за границы, такое как крупные тени или подсказки, расположенные внутри карточки, может быть обрезано по краю карточки.

    Два менее известных нюанса

    Скрытое значение

    content-visibility: hidden пропускает отрисовку элемента, подобно display: none, но браузер сохраняет состояние его отрисовки в кэше. Показ элемента снова значительно проще, чем восстановить отрисовку элемента с display: none, поскольку не нужно начинать всю работу заново. Это делает его полезным для вкладок, меню за пределами экрана и виртуальных прокручиваемых областей. В отличие от auto, контент, находящийся в режиме hidden, недоступен для поиска внутри страницы во время скрытия.

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

    Каждый раз, когда элемент с content-visibility: auto переходит из режима пропуска отрисовки в режим отображения, браузер отправляет событие contentvisibilityautostatechange. Подключение обработчика к этому событию позволяет приостановить ресурсоемкие скрипты, такие как отрисовка на canvas, для контента, который браузер всё равно не отображает.

    Проверка эффекта на собственных страницах

    Небольшой эксперимент наглядно показывает разницу. Создайте тестовую страницу с примерно 1 000 карточками и переключателем, который добавляет или удаляет content-visibility: auto. Откройте DevTools, перейдите в панель Performance, сделайте запись повторной загрузки при выключенном переключателе, затем снова сделайте запись при его включенном состоянии и сравните фиолетовые блоки Layout в обоих отчетах.

    Затем примените ту же проверку к вашей самой длинной странице в продакшене. Запишите отчет во время её загрузки. Когда на большую часть времени уходит обработка Layout, а интерактивность появляется поздно, добавление content-visibility: auto к повторяющимся элементам часто является самым простым и дешевым способом улучшения: одно свойство, без переписывания кода и без миграции на другую фреймворковую среду.

    Основные выводы

    • Браузеры обрабатывают стили и макет всего документа; параметр content-visibility: auto позволяет им отложить эту обработку для элементов, находящихся далеко от области отображения, без удаления чего-либо из DOM.
    • Всегда используйте его вместе с параметром contain-intrinsic-size: auto <estimate> в одном изменении, иначе ползунок прокрутки может скачком переместиться.
    • Контент остается индексируемым, поисковым и доступным, что отличает этот подход от методов загрузки контента по мере прокрутки и виртуализации.
    • Это сокращает время отрисовки, но не объем памяти; когда сам DOM становится слишком большим, виртуализация — правильное решение.
    • Избегайте использования этого подхода над основной частью контента, для элементов, геометрия которых измеряется до отображения, а также для компонентов, отрисовывающихся за пределами своей области.