Анимированная выпадушка на CSS

• css, html

details и summary

Начнём с вёрстки:

<details>
    <summary>Заголовок</summary>
    Содержимое
</details>
Заголовок Содержимое

<details> и <summary> появились в браузерах уже давно (10+ лет назад). Комбинация тегов предлагает хороший, семантический и со всех сторон обкатанный механизм. “Заголовок” виден всегда и кликабелен, а “содержимое” переключается по клику.

Раскрывается без единой строчки JS – да.

Раскрывается от поиска по страница – да.

Поддерживает клавиатуру, скринридеры – тоже да.

Поэтому, если нужно делать раскрывающийся по месту элемент, details+summary – первый претендент на реализацию.

Псвевдоэлемент details-content

::details-content
131
143
18.4

При ближайшем рассмотрении у вёрстки выше есть минус: если мы хотим анимированно скрывать/показывать контент, нам нужно на что-то повесить анимацию. И у нас может быть много контента:

<details>
    <summary>Заголовок</summary>
    <p>...</p>
    <p>...</p>
    <p>...</p>
</details>

По данной вёрстке никакого единого контейнера для трёх параграфов нет. Для решения данной проблемы был создан псевдоэлемент ::details-content. Его нет в явном виде в HTML (как и других псевдоэлементов), однако он охватывает именно то, что нам нужно:

<!-- псевдовёрстка -->
<details>
    <summary>Заголовок</summary>
    <::details-content>
        <p>...</p>
        <p>...</p>
        <p>...</p>
    </::details-content>
</details>

interpolate-size

Давняя проблема: transition и animation не могу анимировать значения вида height: auto (например, между auto и 0). Но в какой-то момент браузеры решили эту задачу, но уже не смогли включить это поведение по умолчанию. Так появилось свойство interpolate-size. У него буквально два значения: дефолтное поведение numeric-only и новое allow-keywords.

.block {
    interpolate-size: allow-keywords;
    transition: height .3s ease;
    height: 0;
}
 
.block_opened {
    height: auto;
}

Свойство позволяет анимировать не только auto, но и min-content, fit-content, max-content и content (для расчётов с flex-basis). Однако анимировать между двумя ключевыми словами всё ещё нельзя, одно из значений обязательно должно быть числовым.

В ченжлоге Safari Technology Preview 254 написано, что добавлена поддержка данного свойства, однако у меня так и не заработало. Судя по всему, работа идёт.

Итоговый результат

Заголовок

Содержимое

Содержимое

Содержимое

В браузерах, в которых нет поддержки interpolate-size, будет просто выпадушка без анимации.

Несколько подводных камней, с которыми можно столкнуться:

1) Анимация работает только при открытии.

Это происходит из-за того, что в браузерных стилях есть content-visibility: hidden для ::details-content в скрытом состоянии. Когда details раскрывается, этот стиль исчезает мгновенно, и элемент может анимироваться из одного состояния в другое. А когда закрывается, стиль тоже появляется мгновенно, и элемент просто мгновенно скрывается.

Это можно победить, если использовать transition-behavior: allow-discrete:

::details-content {
    transition: .3s ease;
    transition-property: height, content-visibility;
    transition-behavior: allow-discrete;
}
transition-behavior
117
129
17.4

Свойство меняет работу анимации свойств, которые можно менять только дискретно (отсюда и название). Без него установленное дискретное свойство сразу применится и скроет содержимое, а с allow-discrete новое значение применится позже и анимация скрытия заработает.

2) Скрытие стрелочки в summary

Казалось бы, в браузеры добавили псевдо-элемент ::marker, именно его и видно в девтулзах внутри summary. Используем примитивный CSS:

summary::marker {
    display: none;
}

И… не работает. Элемент всё равно виден. Чтобы решить данную проблему, я когда-то нашёл костыль с list-style: none:

summary {
    list-style: none;
}

Это успешно работает как в новых браузерах, так и в старых без поддержки ::marker. Но лучше использовать и то и то, ведь list-style явно не предназначается для summary.

Какие ещё варианты есть?

Если не использовать interpolate-size, то есть два хороших варианта:

1) JS. Вычисляем размеры, устанавливаем, готово. Ничего сложного, всё понятно.

2) grid и анимация ячеек через fr. Дело в том, что при несложной комбинации свойств 1fr начинает значить “размер содержимого”. И такое значение уже можно анимировать!

Однако у него есть ряд побочных эффектов, что негативно влияет на итоговый результат. Рассинхрон высоты во время анимации, вёрстка на чекбоксе – примеры подобных проблем.

Выводы

Шёл 2026й год, и мы уже почти получили анимированный спойлер (выпадушку) в двух браузерах. Ждём 2027й, чтобы это заработало в 3х основных браузерных движках?

Хотелось бы верить. А фича с interpolate-size очень мощная. И мы наконец можем анимировать показ элемента после снятия display: none.

Обсудить в Telegram

Почитать ещё посты

  • frame-sizing не решает проблемы размера ифреймов?
  • Отслеживание поддержки фич
  • За пределы вьюпорта мобильного браузера
  • Будущие новинки из CSS Navigation Matching
  • Поддержка фич, которые иначе отследить нельзя: named-feature