Статические маркетинговые сайты по-прежнему отгружают аккордеоны, раскрывающиеся панели и фильтры, которым нужно плавно вырасти от нуля до естественной высоты без таблицы пикселей на каждый брейкпоинт. Долгое время честный ответ был «измерить в JavaScript и задать пиксели», потому что CSS считал height: auto неинтерполируемым ключевым словом на этапе computed value. В 2026 связка interpolate-size: allow-keywords и calc-size() наконец даёт декларативный мост между интринсик-размерами и математикой transition — но только если вы понимаете каскад, взаимодействие с дефолтами flex/grid и то, как WebKit планирует reflow. Здесь — боль, примитивы, прогрессивное улучшение через @supports, перфоманс, доступность и матрица решений, чтобы статические бандлы оставались лёгкими, а motion — предсказуемым. Для сравнения с кросс-документными переходами читайте руководство по View Transitions для статических MPA; если внутри раскрытия есть автоматически растущие поля форм, сочетайте материал с field-sizing для статических форм, чтобы анимации высоты не боролись с интринсиком textarea.
Экономика проверки важна: воспроизвести субпиксельную вёрстку на тех же GPU, что у клиентов, дешевле на арендованном Mac mini, чем выкатить сломанную анимацию. MacHTML держит облачные Apple Silicon хосты около $16.9 в день — эту цифру мы используем как ориентир бюджета репетиций ниже.
Почему анимировать height:auto было больно
Классические transition интерполируют длины, проценты и часть transform, но height: auto особенный: used value появляется только после измерения контента. Если задать transition: height 240ms ease между 0 и auto, многие движки просто щёлкают в конце — нет пары чисел для интерполяции. В ответ придумали max-height: 9999px, что кажется гладким, пока перевод не раздует текст и длительность перестанет соответствовать реальной глубине, или пока дерево лейаута не решит, что коробка потенциально бесконечна. scrollHeight в JavaScript работает, но возвращает main-thread, усложняет CSP на статике и ломается при поздней подгрузке веб-шрифтов.
Новая модель держит измерение внутри стилей: вы всё ещё думаете интринсиком, но даёте движку числовой коридор. Это критично для статических экспортов без React и где каждый байт main.js на счету. Для команд Safari-first важно помнить, как WebKit защищался от циклических колебаний, когда процентные высоты смешивались с интринсиком во flex — понимание спецификации спасает от ложных «багов браузера».
Ещё один нюанс — overflow: hidden на скруглённых карточках: при max-height тени и outline могут расползаться относительно reveal, давая «двойную дверь» на Retina. Интринсик-интерполяция держит used height ближе к реальному content box, если не крутить лишние свойства параллельно.
interpolate-size: allow-keywords на практике
interpolate-size подключает поддерево к интерполяции ключевых слов, которые раньше были нечисловыми. На корне disclosure сообщаете движку, что переходы между интринсиком и длиной можно измерять, если остальные longhands согласны. Обычно свойство ставят на .disclosure или обёртку details, а не на весь body, иначе расширяется поверхность интерполяции и посторонние правила внезапно становятся дорогими.
.disclosure {
interpolate-size: allow-keywords;
overflow: clip;
transition: height 260ms cubic-bezier(.2,.8,.2,1);
}
Сочетайте свойство с явными состояниями height: свёрнуто 0, развёрнуто может оставаться auto, но теперь движок строит числовую траекторию. При переключении классов height: 0 против height: auto следите за collapse маргинов: соседние margin могут оставить «фантомный» зазор — лучше padding на внутреннем wrapper. interpolate-size наследуется: удобно для вложенных аккордеонов, но опасно в библиотеке компонентов, если дочерняя карточка случайно наследует allow-keywords и одновременно анимирует flex-basis — Safari сделает лишние проходы layout. Ограничьте утилитой и убирайте со статических оболочек без motion.
calc-size(fit-content) как мост измерений
calc-size() оборачивает интринсик-ключевое слово в выражение, дающее длину, которую может сэмплировать transition. Типичный аккордеон: развёрнуто height: calc-size(fit-content, size), чтобы браузер измерил блок как при height: auto, а свёрнуто 0 или фиксированный заголовок — тогда интерполируются две длины, а не длина и ключевое слово.
.panel[data-open="true"] .panel-body {
height: calc-size(fit-content, size);
}
.panel[data-open="false"] .panel-body {
height: 0;
}
Переходящим с max-height стоит сравнить ощущаемую скорость: calc-size() следует реальной глубине текста, поэтому easing честен и для коротких ответов, и для длинных политик. Минус — чувствительность к динамике: live region, добавляющая текст во время анимации, сдвигает цель (корректно, но может тянуть easing, рассчитанный на фиксированную дистанцию). На статике замораживайте DOM до transitionend, если не проектируете mid-flight reflow.
С padding помните про box-sizing: при * { box-sizing: border-box; } убедитесь, что измеренная высота совпадает с ожидаемым border box, иначе Safari анимирует content box, а бордер «прыгнет» кадром.
Прогрессивное улучшение с @supports
Дайте слой fallback: без calc-size() оставьте мгновенное переключение или max-height clip. Тестируйте именно токен функции в @supports, а не общее свойство — частичные реализации могут парсить, но не интерполировать.
@supports (height: calc-size(fit-content, size)) {
.panel-body { transition: height 240ms ease; }
}
@supports not (height: calc-size(fit-content, size)) {
.panel-body { transition: max-height 320ms ease; max-height: 0; }
.panel[data-open="true"] .panel-body { max-height: 80vh; }
}
Можно печатать результат @supports в HTML-комментарий на билде для саппорта, но не прячьте контент только за современным CSS: аккордеоны должны оставаться доступны без стилей. Motion — опциональное улучшение поверх семантики details/summary или кнопок с aria-expanded.
Сетка, flex и min-height:auto
У flex-элементов по умолчанию min-height: auto, они не сжимаются ниже интринсик-минимума. Для анимации от 0 до интринсика часто нужен min-height: 0, чтобы свёрнутое состояние было честным без overflow-спама. В grid аналогично min-size:auto и размеры дорожек: строка может отказаться схлопываться, если дети дают большой минимум.
Если disclosure внутри области с align-self: stretch, блочный размер grid-item определён даже при интринсик-контенте — это меняет разрешение calc-size(), иногда ускоряя layout, иногда вступая в конфликт с aspect-ratio. Для маркетинговых лендингов чаще берите одноколоночную сетку тела статьи и изолируйте анимированные панели с contain: layout после проверки, что фокус-кольца не клипуются.
Смешивание анимации gap и высоты заставляет Safari делать два зависимых layout кадра, если ещё крутить margin-block-end у соседей. Оставьте margin статичным на время height tween, мягкий вход сделайте opacity.
Заметки по Safari и WebKit
Движок анимаций WebKit сливает обновления стиля, если main thread занят — высота может дёрнуться даже на M3. Статика не должна блокировать, но аналитика всё равно может джанкать: откладывайте third-party ниже сгиба или за idle callback. Сравнивайте stable Safari и STP, если релиз пересекается с волной обновлений macOS, и фиксируйте пиксельные диффы.
Аппаратное ускорение не переносит height на композитор — layout остаётся. Одновременный transform: translateY() и height удваивает работу, если не поднять слой с осторожным will-change: transform. На один жест — одна примитива: либо slide transform, либо reveal по высоте.
Для субпиксельного текста держите одинаковые настройки сглаживания в свёрнутом и развёрнутом состояниях, иначе QA увидит «мерцание» из-за инвалидации кэша bitmap.
Производительность: thrash и слияние
Каждый сэмпл высоты тянет layout. Пять одновременно открытых панелей умножают стоимость. Ограничьте параллельность, слегка разнесите по времени или пометьте внеэкранные панели content-visibility: auto после проверки доступности. Смотрите фиолетовые полосы layout в таймлайне WebKit шире кадра.
Если остаётся JS fallback с ResizeObserver, не читайте layout синхронно внутри колбэка во время CSS transition — получите петлю. Троттлите через requestAnimationFrame и пропускайте записи, пока document.hidden.
Доступность и prefers-reduced-motion
Часть пользователей чувствительна к вестибулярной нагрузке. При prefers-reduced-motion: reduce сокращайте длительности почти до нуля или заменяйте на лёгкий opacity-хинт, не убирая контент и порядок DOM.
@media (prefers-reduced-motion: reduce) {
.panel-body { transition: none !important; }
}
Клавиатурным пользователям нужны предсказуемые кольца фокуса: если панель клипует outline, используйте overflow: clip с внутренним padding или перенесите outline на внутренний wrapper. aria-expanded обновляйте синхронно со сменой состояния, а не только после конца анимации, если UX явно не требует задержки (обычно нет).
Паттерны статического HTML
Для статического экспорта держите один класс-контейнер и состояние в data-open через минимальный JS или нативные details с зеркалированием в CSS. Без JS details дают открытие/закрытие, а interpolate-size + calc-size() наконец согласуют семантику и motion. Не смешивайте две системы высоты — правила будут драться.
Слои CSS: типографика, затем layout, затем motion — так маркетинговый !important не отключит transition в релизный день. На S3/CloudFront нет фичефлагов, держите motion в отдельном motion.css?v= для контролируемого bust кэша.
Матрица: когда анимировать высоту
| Сценарий | Интринсик tween? | Заметка |
|---|---|---|
| Юридический аккордеон | Да | calc-size следует глубине; без max-height гадания. |
| Бесконечная лента | Редко | Виртуализируйте; высота ломает scroll метрики. |
| Модалки | Иногда | Вход/выход лучше transform; высота для контента. |
| Sticky nav | Осторожно | Конфликт с compositing sticky; тест Safari. |
Нумерованный чеклист QA
- Проверьте высоты 320/390/834 px с самыми длинными локалями.
- Включите prefers-reduced-motion в macOS и убедитесь, что состояние понятно.
- Откройте пять панелей подряд и следите за CPU на базовом M2.
- Снимите таймлайн WebKit с теми же third-party скриптами, что в проде.
- Проверьте порядок фокуса в середине transition внутри clip.
- Сравните stable Safari и STP при окне обновления macOS.
- Перетестируйте после изменения preload шрифтов.
- Снимите диффы для тёмной темы и повышенного контраста.
FAQ
max-height ещё ок?
Да как fallback, но calc-size честнее на длинных текстах.
Заменяет scroll-driven анимации?
Нет; это разные механизмы — комбинируйте с профилированием.
А width:auto?
Похожие идеи, но горизонтальный reflow текста дороже — меряйте.
Надёжный motion на статике — это репетиция на реальном Safari с прод-шрифтами, расширениями и масштабом дисплея. Mac mini от MacHTML за ~$16.9 в день даёт постоянную мишень как у клиентов, с SSH для скриншотов и VNC для дизайнеров.
Репетиция анимаций высоты на облачном Mac mini
Загрузите статический бандл в Safari на Apple Silicon, профилируйте штормы аккордеонов и подпишите CSS до merge.