У свойства overflow долго было четыре рабочих значения: visible, hidden, scroll и auto. Все, кому нужно было просто отрезать вылезающее содержимое, по привычке писали overflow: hidden — и получали побочный эффект, о котором редко задумывались. Значение clip закрывает ровно эту дыру: оно обрезает контент и ничего больше не делает. Разберём, чем оно отличается от hidden, зачем понадобилось отдельное значение и почему именно им лечат сломанный position: sticky.
Зачем придумали отдельное значение
Проблема hidden в том, что он делает сразу две вещи. Во-первых, прячет всё, что выходит за границы блока. Во-вторых — и это неочевидно — превращает элемент в контейнер прокрутки. Скроллбара не видно, но технически блок становится прокручиваемым: к нему применимы scrollTop, scrollIntoView, фокус на скрытом элементе уводит вьюпорт блока, а сам блок создаёт новый контекст форматирования.
В большинстве случаев нужна была только первая половина — обрезать. Вторая половина приходила бесплатным и часто вредным довеском. Поэтому в модуле CSS Overflow появилось значение clip: оно обрезает по так называемому краю обрезки (overflow clip edge), но не создаёт контейнер прокрутки, не показывает скроллбар, запрещает любую прокрутку — включая программную — и не открывает новый контекст форматирования.
/* было: обрезали — и заодно случайно сделали блок прокручиваемым */
.card {
overflow: hidden;
}
/* стало: чистое намерение «просто отрезать лишнее» */
.card {
overflow: clip;
}
Если контекст форматирования всё-таки нужен (например, чтобы блок охватывал плавающих потомков), его добавляют явно через display: flow-root — а не как побочный эффект обрезки.
clip против hidden: построчное сравнение
Визуально результат одинаковый: вылезающее содержимое не видно, скроллбара нет, до спрятанного не добраться мышью. Различия — в поведении, которое не видно глазом.
| Поведение | overflow: hidden | overflow: clip |
| Прячет вылезающее содержимое | да | да |
| Создаёт контейнер прокрутки | да | нет |
| Программная прокрутка (scrollTop, scrollIntoView) | работает | запрещена |
| Новый контекст форматирования | создаёт | не создаёт |
| Влияет на position: sticky у потомков | да (часто ломает) | нет |
| Поддерживает overflow-clip-margin | нет | да |
Главный практический вывод из таблицы — строчка про контейнер прокрутки. Именно из неё растут и поломка залипания, и трюк с поосевой обрезкой, к которым мы сейчас перейдём.
overflow-clip-margin: рисуем чуть дальше края
По умолчанию clip режет ровно по padding-боксу. Но иногда нужно, чтобы что-то выходило за край на несколько пикселей — тень, обводка при фокусе, декоративный хвостик — и только потом обрезалось. Для этого есть парное свойство overflow-clip-margin: оно отодвигает край обрезки наружу на заданную длину.
.badge-host {
overflow: clip;
/* содержимое видно ещё на 1.5rem за краем, дальше — отрез */
overflow-clip-margin: 1.5rem;
}
Свойство работает только вместе с overflow: clip. На hidden, auto или scroll оно просто игнорируется — ещё одна причина, по которой clip не сводится к косметическому синониму hidden. Отрицательные значения не допускаются: внутрь padding-бокса край обрезки не сдвинуть.
Почему clip чинит position: sticky
Это самый частый повод узнать про clip. Сценарий знаком многим: вешаешь на заголовок position: sticky с top: 0, он должен залипать при прокрутке страницы — а он упрямо уезжает вверх вместе с контентом. Чаще всего виноват какой-то родитель выше по дереву, которому ради обрезки декора или гашения горизонтального скролла прописали overflow: hidden.
Механика такая. Залипающий элемент привязывается не к окну, а к ближайшему предку-контейнеру прокрутки. Пока такого предка нет, контейнером считается само окно — и заголовок честно залипает к верху вьюпорта. Но как только промежуточный родитель получает overflow: hidden, он сам становится контейнером прокрутки. Теперь top: 0 отсчитывается уже от него, а раз этот родитель не прокручивается (его контент помещается целиком), залипать просто негде — элемент ведёт себя как обычный.
/* родитель ради обрезки декора стал контейнером прокрутки —
и невольно сломал залипание заголовка внутри */
.section {
overflow: hidden; /* ← заменить на clip */
}
.section__heading {
position: sticky;
top: 0;
}
Замена одной строки на overflow: clip чинит всё: декор по-прежнему обрезается, но родитель больше не контейнер прокрутки, поэтому залипание снова отсчитывается от окна. Никаких JavaScript-костылей и переноса разметки не требуется.
.section {
overflow: clip; /* обрезка осталась, контейнер прокрутки — нет */
}
Тот же диагноз стоит держать в голове, когда залипание ломается у элемента внутри карточек, слайдеров и любых обёрток с обрезкой — почти всегда дело в случайном overflow: hidden на промежуточном блоке.
Где ещё пригодится overflow: clip
Починка залипания — не единственная причина. Несколько кейсов, где clip объективно лучше hidden.
Обрезать по одной оси, выпустить по другой
С hidden это невозможно: стоит задать overflow-x: hidden, как браузер принудительно поднимает вторую ось с visible до auto, и по вертикали появляется скролл. С clip пара overflow-x: clip и overflow-y: visible работает буквально: лишнее по горизонтали отрезается, а тултипы, бейджи и тени спокойно вылезают сверху и снизу.
.row {
overflow-x: clip; /* паразитный горизонтальный вылет — под нож */
overflow-y: visible; /* «NEW», тултипы, тени по вертикали — наружу */
}
Погасить случайный горизонтальный скролл страницы
Классическая боль: один широкий блок (таблица, ушедшая в минус секция, длинное слово) растягивает страницу, и снизу появляется паразитный горизонтальный скроллбар. Привычное лекарство — overflow-x: hidden на body — превращает body в контейнер прокрутки и попутно ломает любое залипание на странице. overflow-x: clip гасит вылет, не трогая прокрутку.
body {
overflow-x: clip; /* было overflow-x: hidden — ломало sticky */
}
Декор и анимации за краем карточки
Картинка с лёгким зумом по наведению, угол-ленточка «Sale», блик, уезжающий за границу — всё это надо обрезать по контуру карточки. Раньше для этого хватало hidden, но если внутри той же карточки есть залипающий элемент или нужно выпустить тень по одной оси — clip снимает конфликт. А если речь о фигурной, а не прямоугольной обрезке — это уже задача для свойства clip-path, которое режет по произвольному контуру.
Чуть меньше работы для браузера
Контейнер прокрутки — это не только поведение, но и накладные расходы: браузер закладывает возможность скролла, считает прокручиваемую область. Когда прокрутка заведомо не нужна, clip избавляет от этой бухгалтерии. На одном блоке разница незаметна, но в длинных списках карточек мелочь складывается.
Подводные камни
- clip по одной оси рядом с auto/scroll по другой ведёт себя как hidden. Поосевой фокус работает только в паре clip + visible (или clip + clip). Если вторая ось — auto, scroll или hidden, то clip молча превращается в hidden и снова создаёт контейнер прокрутки.
- Нет программной прокрутки. Если к блоку планируется scrollIntoView, scrollTo или прокрутка к сфокусированному потомку — clip не подойдёт, нужен hidden, auto или scroll.
- overflow-clip-margin без clip не работает. На hidden/auto/scroll свойство просто игнорируется, отрицательные значения недопустимы.
Итог
Правило простое. Нужно просто обрезать вылезающее, без прокрутки и без сюрпризов — берём overflow: clip. Нужен реально прокручиваемый блок (явный скроллбар или прокрутка из JavaScript) — остаётся hidden, auto или scroll. А когда сломалось залипание — первым делом ищем родителя с overflow: hidden и меняем его на clip. Одно значение, минус целый класс трудноуловимых багов.
Комментарии (0)