Аккордеон и раскрывающееся меню с анимацией

Height нельзя анимировать напрямую между 0 и auto - transition умеет интерполировать только числа, а auto для браузера не число, а команда "посчитай по контенту". Старое решение - max-height с заведомо большим значением, но оно тратит впустую большую часть настроенной длительности на невидимый рост. Современное - display:grid и grid-template-rows: 0fr → 1fr, который анимируется точно до реальной высоты контента, без подбора цифр на глаз.

Попробуй прямо сейчас: клик по подписи "Что такое CSS Grid?" переключает чекбокс, а .accordion-toggle:checked ~ .accordion-panel меняет grid-template-rows с 0fr на 1fr - панель плавно раскрывается ровно до высоты своего текста. Смени 0.4s на 1.5s, чтобы разглядеть саму механику подробнее, или временно убери overflow:hidden у .accordion-inner - текст на миг "вылезет" наружу до того, как трек сетки успеет вырасти.

Почему height не анимируется напрямую: max-height и grid-template-rows

transition умеет плавно менять только то, что браузер способен посчитать как последовательность промежуточных числовых кадров (тема уже разбиралась в уроке про transition-property). height: auto - это не число, а инструкция "вычисли высоту по контенту", поэтому transition: height между 0 и auto просто не сработает: браузер либо мгновенно перескочит, либо анимация вообще не запустится. Старый обходной путь - max-height, у которого можно смело задать любое число, например max-height: 3000px, и оно всегда останется числом, а не auto.

.panel { max-height: 0; overflow: hidden; } .panel.open { max-height: 3000px; } /* transition: max-height 1s */ - работает, но 3000px почти всегда намного больше реального контента, а значит большая часть настроенной 1s тратится впустую на невидимый рост цифры дальше вверх уже после того, как контент полностью раскрылся.

Собственная проверка (Playwright, блок с реальной высотой контента 716px, max-height: 0 → 3000px, transition 1s linear): панель достигла своей настоящей видимой высоты уже на 0.26 секунды из настроенной 1 секунды - то есть всё видимое раскрытие уложилось в первую четверть времени, а оставшиеся 74% длительности ушли на рост max-height туда, где контента уже давно нет. Отсюда и распространённая жалоба "анимация раскрытия выглядит гораздо резче, чем я задал в transition" - это не баг, а прямое следствие того, насколько щедро выбрано "запасное" число.

Современная альтернатива не требует угадывать число вообще:

.panel { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 0.4s ease; } .panel-inner { overflow: hidden; } .panel.open { grid-template-rows: 1fr; } - трек сетки анимируется от 0fr до 1fr, а браузер сам вычисляет, сколько это в пикселях, исходя из реального контента внутри .panel-inner.

Собственная проверка (Playwright, два независимых блока с разной реальной высотой контента - 84px и 356px, измерено заранее через scrollHeight): после срабатывания grid-template-rows: 0fr → 1fr итоговая высота обёртки точно совпала с реальной высотой контента в обоих случаях, без единого подобранного вручную числа. Работает это благодаря тому, что браузеры умеют плавно интерполировать fr-единицы в grid-template-rows и grid-template-columns - возможность давно и надёжно поддерживается во всех актуальных браузерах (Chrome/Edge с 2022 года, Firefox ещё раньше, Safari с 2022 года).

Собираем аккордеон и меню без JavaScript

Переключать состояние "открыто/закрыто" без единой строчки JavaScript помогает чекбокс-хак: скрытый input[type="checkbox"], связанный с ним через for/id label (он же и есть видимый кликабельный триггер - клик по label переключает чекбокс), и соседний блок с контентом, который стилизуется через селектор :checked и комбинатор соседства (~ или +). Ровно та же связка label-как-триггер работает и для мыши, и для клавиатуры (label наследует фокус input), и для тач-экранов - но на тач-устройствах, как уже отмечалось в уроке про анимированные кнопки и ссылки, стоит проверять поведение отдельно от десктопного hover.

<input type="checkbox" id="q1" class="toggle"> <label for="q1" class="toggle-label">Вопрос</label> <div class="panel">...</div> .toggle { position: absolute; opacity: 0; } .toggle:checked ~ .panel { grid-template-rows: 1fr; } - .toggle:checked ~ .panel находит панель по признаку "соседний элемент после отмеченного чекбокса", не завязываясь на JavaScript.

.toggle:checked .panel { grid-template-rows: 1fr; } - без ~ или + браузер ищет .panel ВНУТРИ чекбокса (а внутри пустого input вложенных элементов нет), правило никогда не сработает.

Ровно та же связка чекбокс + label + :checked ~ работает и для выпадающего меню - разница только в контенте панели (список ссылок вместо текста ответа) и в том, что чекбокс здесь удобнее спрятать за иконкой-гамбургером через сам label, а не через видимый текст.

Важная оговорка про доступность: чекбокс-хак решает задачу визуально, но не идеально семантически. Экранный диктор объявит скрытый input как обычный чекбокс - "отмечено" или "не отмечено", а не как "развёрнуто" или "свёрнуто" для секции. Там, где нужна безупречная встроенная семантика без единого атрибута ARIA, у HTML уже есть готовый инструмент - теги <details> и <summary>, браузер сам объявляет диктору правильное состояние. Чекбокс-хак остаётся рабочим и распространённым паттерном именно там, где нужна кастомная анимация раскрытия, которую голый <details> долгое время не умел красиво анимировать при закрытии.

Частые ошибки новичков

ОшибкаЧто происходитКак исправить
Писать transition: height 0.3s и менять height: auto ↔ height: 0 Анимация не запускается или срабатывает мгновенным скачком - auto не число Использовать max-height с фиксированным числом или grid-template-rows: 0fr → 1fr
Брать max-height впритык к реальной высоте контента (например 200px для блока на 190px) При малейшем увеличении текста (перевод, смена шрифта, добавленный пункт) низ панели обрезается Либо брать max-height с запасом минимум 2x, либо сразу перейти на grid-template-rows
Брать max-height с огромным запасом "на всякий случай" (например 3000px для блока на 200px) Большая часть настроенной длительности transition тратится на невидимый рост - раскрытие выглядит резче задуманного Использовать grid-template-rows: 0fr → 1fr - он анимируется ровно до реальной высоты контента
Забыть overflow: hidden на внутреннем блоке с контентом при grid-template-rows Пока трек сетки ещё сжат (0fr), текст всё равно видно - он "вылезает" наружу поверх соседних блоков Всегда оборачивать контент в div с overflow: hidden внутри трека grid-template-rows
Стилизовать панель через .toggle:checked .panel (потомок) вместо .toggle:checked ~ .panel (сосед) Правило не срабатывает - внутри пустого input вложенных элементов нет Использовать комбинатор соседства ~ или + между чекбоксом и панелью

Шпаргалка

ЗаписьЧто означает
max-height: 0 → Npx + transitionСтарая техника, требует угадывать число, тратит время анимации впустую
display:grid; grid-template-rows: 0fr → 1frСовременная техника - анимируется точно до реальной высоты контента
overflow: hidden на внутреннем блокеОбязателен для grid-template-rows - прячет контент, пока трек сжат
input[type=checkbox] + label[for] + :checked ~Переключение открыто/закрыто без единой строки JavaScript
<details><summary>Нативная альтернатива с готовой доступностью, но менее гибкая в анимации

Коротко: отвечаем на вопросы

Почему нельзя просто написать transition: height 0.3s и анимировать открытие блока?

transition умеет плавно менять только числовые значения, которые браузер может пересчитывать кадр за кадром. height: auto - это не число, а инструкция "вычисли высоту по контенту", поэтому переход между auto и 0 не анимируется: браузер либо переключает мгновенно, либо анимация вовсе не запускается. Нужен либо max-height с конкретным числом, либо grid-template-rows: 0fr → 1fr.

Правда ли, что max-height с большим "запасным" значением полностью решает проблему анимации высоты?

Только визуально, но не по длительности. Собственная проверка на блоке с реальной высотой контента 716px и max-height: 0 → 3000px (transition 1s linear) показала, что панель достигает настоящей видимой высоты уже на 0.26 секунды из настроенной секунды - то есть около 74% заданной длительности уходит на невидимый рост max-height дальше вверх, где контента уже нет. Раскрытие визуально выглядит намного резче, чем задано в transition.

Чем grid-template-rows: 0fr → 1fr лучше max-height для анимации раскрытия?

Не нужно угадывать число. Собственная проверка на двух блоках с разной реальной высотой контента (84px и 356px) показала, что после срабатывания grid-template-rows итоговая высота обёртки точно совпадает с реальной высотой контента в обоих случаях - браузер сам вычисляет нужное значение в пикселях, интерполируя fr-единицы, а не растягивая заранее подобранное число.

Доступен ли аккордеон на чекбоксах для пользователей с экранным диктором?

Не полностью. Диктор объявляет скрытый input как обычный чекбокс - "отмечено" или "не отмечено", а не как "развёрнуто"/"свёрнуто" для секции, как это делает нативный тег details. Чекбокс-хак остаётся рабочим и распространённым решением там, где важна именно кастомная CSS-анимация раскрытия; там, где важнее готовая семантика "из коробки", стоит рассмотреть <details><summary>.

Категория
Поделиться в социальных сетях
1. Переходы: transition
2. Трансформации: transform
3. Ключевые кадры и animation
4. Фильтры: filter
5. Практика: живые интерфейсы
6. Производительность и доступность

Отправить комментарий

0 Комментарии

Отправить комментарий (0)

#buttons=(Хорошо) #days=(20)

Мы используем файлы cookie для улучшения работы сайта. Подробнее
Хорошо
×