Вы всё ещё устанавливаете display:none по таймауту? Тогда мы идём к вам

Дисклеймер: хотел создать скромный пост, а не статью, но не справился с управлением новым редактором. А старый редактор не поддерживает посты.

Допустим, у нас есть блок (скажем, бутстраповская ячейка

) и мы хотим её схлопывать (скажем, по клику на кнопке).

image

Пишем пару простых классов:

:root
{
…
--fast: 0.4s;
--all-fast-ease: all var(--fast) ease;
…
}

.all-fast-ease
{
    transition: var(--all-fast-ease);
}

.collapsed
{
    overflow: hidden;
    height: 0;
}

Помечаем наш блок классом all-fast-ease, чтобы он сворачивался плавно:

Затем добавляем класс collapsed в коде обработчика (везде используется jQuery):

$('.contact-form .hugh-mann-detector').on("click", function ()
{
…
    if (100 == progressValue) // Пять кликов, progressValue += 20
    {
        $(this)
            .data('is-hugh-mann', true)
            .parent().addClass('collapsed');
    }
}

И… ничего не происходит. Всё правильно: мы пытаемся анимировать высоту (height) к значению 0 из значения auto. А для этого нам требуется явно сообщить о своих намерениях:

.collapsed
{
    overflow: hidden;
    height: 0;
    interpolate-size: allow-keywords; /* Chrome 129+ */
}

Значение allow-keywords поддерживается в Chrome и Edge уже пару версий, и, надо полагать, Firefox с Safari тоже скоро подтянутся.

Однако, если блок содержит элементы управления (а в нашем случае он содержит кнопку), нас ждёт сюрприз. Оформить «детектор Чела Века» (.hugh-mann-detector) именно как кнопку нужно из соображений accessibility. Во-первых, до неё должно быть можно добраться при помощи клавиатуры (Tab), если человеку трудно двигаться. Во-вторых, скринридер должен сообщить о наличии кнопки, которую можно нажать.

Но именно по этой причине кнопка, даже скрытая в блоке нулевой высоты, крадёт клавиатурный фокус. А это говорит о том, что свёрнутый блок надо скрывать полностью, например, задав ему display: none.

Первым делом хочется написать setTimeout и отложенно добавить к нашему блоку класс d-none.

Проблема в том, что для этого нам нужно как-то найти время анимации. Конечно, можно просто явно указать значение (400 миллисекунд), благо мы сами описали анимацию.

image

Но это нарушит принцип DRY («не сотвори себе копипасту»). Проблема осложняется тем, что у нас период времени вынесен в отдельную переменную (--fast), и если мы будем определять её значение, а в разметке all-fast-ease будет заменён на какой-нибудь all-slow-ease, мы об этом не узнаем. Другое осложнение в том, что значение придётся парсить, чтобы получить миллисекунды.

А это значит, что лучше всего анимировать display там же, где происходит и схлопывание. Да, с августа теперь так можно! Создадим специализацию для быстро-анимированного-свёрнутого блока:

.collapsed.all-fast-ease
{
    transition: var(--all-fast-ease), display var(--fast) ease allow-discrete;
}

Теперь display будет анимироваться наравне с высотой, поскольку мы задали поведение allow-discrete. Можно было бы просто объединить его с прочими анимациями (вписав allow-discrete в конец переменной --all-fast-ease), но это нарушило бы ещё один принцип: указывать только то, что нужно (так получается более универсальная стилизация с меньшим количеством сюрпризов).

И теперь вместо setTimeout достаточно написать в обработчике события:

.parent().addClass('collapsed d-none');

Результат вы видели на КДПВ.

© Habrahabr.ru