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


Допустим, у нас есть блок (скажем, бутстраповская ячейка <div class="col-12">) и мы хотим её схлопывать (скажем, по клику на кнопке).


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, чтобы он сворачивался плавно:


<div class="col-12 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');

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

Комментарии (10)


  1. xemos
    27.12.2024 05:11

    1) не проще было не делать display: none кнопке, а оставить её visibility:hidden и запреть фокусироваться на неё через tab-index?

    2) Я правильно понял, что описанное решение использует ультра последние технологии и поэтому ближайшие несколько лет представляет только теоретический интерес? (пока там все актуальные версии сафари начнут его поддерживать)

    3) jquery? зачем?


    1. simple-mortal
      27.12.2024 05:11

      описанное решение использует ультра последние технологии

      Вот этот вопрос тоже всегда беспокоит. Я на это смотрю, как на то, как я буду делать через 2-4 года, пока точно все браузеры на несколько версий назад будут поддерживать эти каттинг-эдж фичи, ибо далеко не все поддерживают браузеры в актуальном состоянии. Особенно на мобильных устройствах. Десктопные как-то вроде сами могут обновляться, а вот на мобилках смотрю иногда у людей на число в красном кружочке на АппСторе и понимаю, что там точно allow-keywords не прокатит :)


    1. ImagineTables Автор
      27.12.2024 05:11

      1. не проще было не делать display: none кнопке, а оставить её visibility:hidden и запреть фокусироваться на неё через tab-index?

      А если у вас там целая форма? Надо будет перебирать все элементы? Я вообще предпочитаю универсальные решения: когда что-то схлопнулось, оно должно перестать отображаться. Тогда будет меньше сюрпризов. Какой, например, сюрприз может ждать нас в случае вашего подхода? Вы, возможно, не сталкивались с тем, что браузер не даёт вам 100% управления табабильностью (tabbable) через tab-index, а я сталкивался. Когда вы подписываетесь на события из списка ['focusin', 'focusout'], умный Хром решает, что вы хотите обидеть тех, кто плохо работает с мышью, и делает элемент tabbable насильно. Глубину проблемы я не знаю (для всех ли элементов так устроено, или только для тех, с которыми я работал), но я знаю, что этих (и других) проблем можно избежать, если не заменять универсальные решения костылями.

      1. Я правильно понял, что описанное решение использует ультра последние технологии и поэтому ближайшие несколько лет представляет только теоретический интерес? (пока там все актуальные версии сафари начнут его поддерживать)

      Во-первых, это зависит от вашей области. Если вы, например, как и я, часто пользуетесь CEF, этот вопрос становится неактуальным.

      Во-вторых, что случится, если все эти «ультра последние технологии» не поддерживаются? Панель будет скрыта мгновенно. «На этот риск я готов пойти». Я читал, некоторые жалуются, что семёрочные ESR-браузеры не дают зайти в личный кабинет из-за того, что дизайнеры не добавили слой совместимости (конкретно — .groupBy() к Object) — а вот так, всё-таки, делать, наверно, не стоит. Хотя, может, разрабы посмотрели, и увидели, что из ста тысяч юзеров только один сидит на ESR. Жестокий бизнес жесток, и исключать подобное нельзя.

      В-третьих, пока вы прочитаете, пока попробуете, «ультра последние технологии» уже станут повседневными.

      И в-четвёртых, можно для совместимости какое-то время выставлять height из скрипта, а потом убрать этот код.

      1. jquery? зачем?

      Затем, что jQuery — это квази-язык, этакий DSL для декларативного описания DOM-преобразований. Как SQL. Как LINQ в дотнете. Лично меня крайне печалит, что вместо того, чтобы добавить стрингитайзеры для синтаксического контроля селекторов (вместо $('div.myclass') — $(div.myclass) с проверкой хотя бы того, что выражение имеет вид селектора), W3C пошёл по императивному пути переменных и циклов. Впрочем, это не единственная претензия к W3C.

      И, между нами, был такой случай, когда человек написал какой-то трёхстраничный кошмар с пачками бойлерплейта (в том числе для установки всех CSS-свойств), а когда его спросили, не хочет ли он переписать в терминах jQuery и сократить код в десять раз, он очень смешно ответил: «Ну да, ну да, буду я ещё свой код говнякать применением jQuery». Я бы дал ссылку, но человек ещё обидится, пожалуй. И такой случай не единичен! Боюсь, тут есть какая-то связь.


      1. un1t
        27.12.2024 05:11

        jQuery может быть может упростить код, но это дополнительная зависимость, которая может прекратить свое существование и придется его потом усердно выпиливать. Я бы не стал тащить его в новые проекты.


        1. ImagineTables Автор
          27.12.2024 05:11

          Есть два типа программистов. Одни тащат библиотеку для определения, является ли число чётным. (Между прочим, в ноябре их набралось два миллиона). Для них, наверно, это будет большой удар, если их библиотека «прекратит своё существование».

          А у других ружьё. А другие берут jQuery в качестве отправной точки, и аккуратно обрабатывают напильником. Вы думаете, jQuery идеален? Вовсе нет. Самый простой пример: допустим, вам надо обновить свойство элемента. В этом случае ортодоксальный jQuery требует кеширования предыдущего значения в переменной, что разрывает chaining (не говоря о введении ненужной переменной). А всё потому, что .data()/.attr()/.prop()/.val() не поддерживают лямбды. Это досадное упущение (и десятки других) для выразительности кода обязательно нужно исправить.

          По сути, jQuery для меня не столько библиотека, сколько стандарт. От него мне нужны стандартные имена (чтобы люди, читающие мой код, видели привычные функции и понимали, что происходит) и дух (декларативность преобразований).

          Поэтому, на вопрос, что я буду делать, если вдруг jQuery «прекратит своё существование», я могу ответить, что буду продолжать делать то, что делаю сейчас: и дальше развивать свой jQuery. Для меня мало что изменится.

          На этом разрешите тему jQuery закрыть.


          1. xemos
            27.12.2024 05:11

            Честно не знал, что про jquery ещё так мыслят (что это стандарт и отправная точка).

            Я без подкола, реально думал что такой подход умер лет 7 назад.

            Спасибо за информацию, что такой взгляд существует. (повторяю, я без подкола)


      1. xemos
        27.12.2024 05:11

        А если у вас там целая форма?

        можно навесить аттрибут inert на форму

        Во-первых, это зависит от вашей области.

        Это очевидно, но автору стоило как-миниму упомянуть что его решение на момент написания не очень кроссбраузерно. а как максимум предложить полифилы/альтернативы

        И, между нами, был такой случай, когда человек написал какой-то трёхстраничный кошмар с пачками бойлерплейта (в том числе для установки всех CSS-свойств), а когда его спросили, не хочет ли он переписать в терминах jQuery и сократить код в десять раз

        Можно пример, как jquery уменьшает код в 10 раз?


  1. un1t
    27.12.2024 05:11

    везде используется jQuery

    Он еще жив?


    1. ImagineTables Автор
      27.12.2024 05:11

      Я ответил выше.


  1. Mnilionic
    27.12.2024 05:11

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

    .collapsed{ animation: hide var(--fast) ease forwards; overflow: hidden; } @keyframes hide { 99% { height: 0px; } 100% { display: none; } }