CSS-свойство contain сообщает браузеру, что содержимое блока изолировано от остальной страницы — и его можно смело оптимизировать, не пересчитывая макет соседей при каждом изменении. По сути, это контракт: разработчик обещает браузеру, что изменения внутри блока не выйдут наружу, а браузер за это применяет более агрессивные оптимизации рендера.

Когда свойство появилось (спецификация CSS Containment Module Level 1), его подавали как чистую оптимизацию. За прошедшие годы contain оброс новыми смыслами: content-visibility предложил ленивый рендеринг off-screen-элементов на его базе, а container queries (@container) вообще без containment не работают. Так что сегодня contain — уже не факультативная фича для performance-любителей, а enabler для стандартных возможностей CSS.

Проблема, которую решает contain

DOM — древовидная структура, где изменение любого узла может каскадно отозваться в макете всех остальных. Когда JavaScript меняет содержимое одного блока (добавляет комментарий, разворачивает аккордеон, обновляет счётчик), браузер не знает, кто из соседей мог задеться — и на всякий случай пересчитывает layout и repaint для гораздо большей области, чем нужно.

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

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

Значения contain — 6 штук и 2 шорт-хэнда

Начиная с CSS Containment Level 2, свойство принимает 6 отдельных значений и 2 шорт-хэнда. Каждое значение «включает» конкретный тип изоляции; шорт-хэнды комбинируют несколько сразу.

layout — изоляция макета

.card { contain: layout; }

Ничто снаружи не влияет на внутренний макет; ничто изнутри не меняет ничего за пределами. Устанавливается новый контекст форматирования блока (BFC): плавающие элементы удерживаются внутри, margin-отступы не схлопываются с соседями, элемент становится containing block для абсолютно-позиционированных потомков. Плюс: создаётся новый stacking context, z-index работает изолированно.

На том же примере с contain: layout плавающий элемент больше не вылезает из карточки:

paint — изоляция отрисовки

.card { contain: paint; }

Включает всё то же, что и layout, плюс обрезает всё, что выходит за границы блока — работает как неявный overflow: hidden для отрисовки. Плавающий элемент, тултип, тень — всё, что должно вывалиться, обрезается по границе блока:

size — фиксация размера

.card {
  contain: size;
  height: 200px;   /* обязательно нужен явный размер */
}

Браузер игнорирует содержимое при расчёте размера блока — ты обещаешь, что размер известен заранее. Если явную высоту не задать, блок схлопнется в ноль (иронично, но именно так):

inline-size и block-size — частичная фиксация размера

Появились в CSS Containment Level 2. Изолируют только одну ось размера: inline-size — ширину (в горизонтальных языках), block-size — высоту. Практический смысл в том, что фиксировать одну ось безопаснее и достаточно для многих кейсов.

.card {
  contain: inline-size;   /* ширина известна, высота — из контента */
  width: 300px;
}

contain: inline-size — это то, что под капотом активирует container queries. Об этом отдельный раздел ниже.

style — изоляция style-scope (deprecated)

Значение style должно было изолировать эффекты счётчиков counter-* и цитат quotes. Работало непоследовательно между движками, и в CSS Containment Level 2 его признали проблемным. Использовать не рекомендуется — для новых проектов пропускай.

Шорт-хэнды content и strict

.card { contain: content; }   /* = layout paint style */
.card { contain: strict; }    /* = layout paint style size */

content — самый безопасный шорт-хэнд, включает layout и paint (плюс устаревший style). Хорошая «default»-опция для изолированных виджетов — карточек, комментариев, аккордеонов.

strict — максимально агрессивный: всё то же плюс size. Требует явно заданного размера, поэтому подходит только там, где блок точно фиксированной высоты (grid-cell, absolutely-positioned overlay).

Какой шорт-хэнд выбрать:

  • Виджет-карточка, комментарий, аккордеон, у которого содержимое может расти — берём contain: content
  • Модалка или тултип с известной высотой, изолированный от родителя — тут пригодится contain: strict с явным height
  • Хочется только контекст для container queries — лучше явно писать container-type: inline-size, чем голое contain: inline-size (у второго нет привязки к @container-разметке)
  • Просто «хочу оптимизацию, не хочу разбираться в нюансах» — contain: content покрывает 80% случаев без побочных эффектов
Поддержка contain браузерами
chrome
Chrome
52
firefox
Firefox
69
edge
Edge
79
safari
Safari
15.4
opera
Opera
40

contain-intrinsic-size — плейсхолдер размера

Из того же модуля CSS Containment, что и contain, есть парное свойство contain-intrinsic-size. Оно решает конкретную проблему contain: size (и особенно strict-шорт-хэнда), где блок без явно указанной высоты просто схлопывается: даёт браузеру «резервный» размер на случай, когда содержимое не участвует в расчёте.

.card {
  contain: size layout;
  contain-intrinsic-size: 0 400px;   /* auto-высота ≈ 400px, ширина от родителя */
}

Синтаксис двухзначный: ширина и высота через пробел; auto префиксом (auto 400px) означает «используй настоящий размер, если он уже известен, иначе — заданный резервный». Полезно везде, где размер известен грубо, но точное значение зависит от контента.

Смежные свойства: content-visibility и container queries

Свойство content-visibility: auto — следующий шаг в этой же спецификации: включает все виды containment (layout, paint, style) плюс пропуск рендера для элементов вне вьюпорта. На длинной ленте даёт кратное ускорение первичной отрисовки. Оно опирается на contain-intrinsic-size, чтобы не было layout shift при появлении блока во вьюпорте. Полный разбор с примерами в отдельной статье.

Второе принципиально важное развитие — container queries (@container). Стилизация компонента в зависимости от размера родителя, а не вьюпорта — ключевой сдвиг в компонентно-ориентированном CSS. И работает это как раз через containment: свойство container-type: inline-size под капотом устанавливает contain: inline-size. Без изоляции размера были бы циклические зависимости «child меняет свой размер → parent пересчитывает свой → child получает новый запрос». Как настраивать и когда применять — читай тут.

Как измерить эффект в DevTools

Гарантированный способ проверить, что contain реально помогает — сравнить длительность layout-фазы в Performance-панели.

  1. Открой DevTools (Chrome / Firefox / Edge) → Performance
  2. Записывай сессию (Record) в момент, когда происходит layout thrash: скролл длинной страницы, обновление содержимого, resize окна
  3. В таймлайне найди события Layout (Chrome их показывает фиолетовыми блоками) и Recalculate Style
  4. Обрати внимание на Nodes affected для каждого Layout — при работающем contain число должно быть меньше, чем без него
  5. Смотри также Long Animation Frame API-события — они подсвечивают кадры, которые заняли больше 50ms; после включения containment таких кадров должно стать меньше

В Chrome DevTools 132+ можно включить оверлей Layout Shift Regions (settings → Rendering) — браузер подсветит регионы, которые пересчитываются при каждом событии. До/после включения contain разница видна глазами.

Где помогает и что ломает

Помогает в реальности:

  • Длинные ленты (комментарии, feed, статьи по scroll) — content-visibility: auto + contain-intrinsic-size
  • Дашборд-виджеты, каждый из которых обновляется независимо — contain: content на карточку
  • Модалки, дропдауны, тултипы — contain: layout paint изолирует их от родительского макета
  • Компоненты с container queries — container-type ставит containment автоматически

Что ломает:

  • contain: paint обрезает выходящее наружу. Тултипы, drop-shadow, любые элементы с position: absolute, вылезающие за пределы блока — будут обрезаны. Проверяй, что ничего важного не спрятано
  • contain: size схлопывает блок без явной высоты. Ошибка новичка — поставить size и увидеть нулевой блок. Всегда пара с явным height или block-size
  • Layout containment создаёт stacking context. Если код полагался на «плоский» z-index через весь документ, включение contain: layout поломает наложение слоёв
  • Не даёт выигрыша, если блок и так изолирован. Элементы с position: fixed, transform, filter, will-change: transform уже создают свой compositor layer — дополнительный contain практически не даёт эффекта

Правило безопасного включения: начни с contain: content (шорт-хэнд layout paint style). Он покрывает 80% полезных случаев и не требует явных размеров. Если хочется большего — переходи на content-visibility: auto с contain-intrinsic-size. strict и голый size оставь для случаев, где размер известен заранее и не меняется.