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-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-панели.
- Открой DevTools (Chrome / Firefox / Edge) → Performance
- Записывай сессию (Record) в момент, когда происходит layout thrash: скролл длинной страницы, обновление содержимого, resize окна
- В таймлайне найди события Layout (Chrome их показывает фиолетовыми блоками) и Recalculate Style
- Обрати внимание на Nodes affected для каждого Layout — при работающем contain число должно быть меньше, чем без него
- Смотри также 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 оставь для случаев, где размер известен заранее и не меняется.
Комментарии (0)