Анимация по высоте: авто; (и другие ключевые слова внутреннего размера) в CSS

Используйте свойство interpolate-size или функцию calc-size() , чтобы обеспечить плавные переходы и анимацию от длин к ключевым словам внутреннего размера и обратно.

Опубликовано: 17 сентября 2024 г.

Введение

Часто запрашиваемая функция CSS — возможность анимации по height: auto . Небольшим вариантом этого запроса является переход к свойству width вместо height или к любому другому внутреннему размеру, представленному такими ключевыми словами, как min-content , max-content и fit-content .

Например, в следующей демонстрации было бы неплохо, если бы метки плавно анимировались до своей естественной ширины при наведении курсора на значки.

Используемый CSS следующий:

nav a {
    width: 80px;
    overflow-x: clip;
    transition: width 0.35s ease; /* 👈 Transition the width */

    &:hover,
    &:focus-visible {
        width: max-content; /* 👈 Doesn't work with transitions */
    }
}

Несмотря на то, что transition объявлен для перехода свойства width , а width: auto объявлена ​​для :hover , плавного перехода не происходит. Вместо этого изменения происходят резко.

Анимация к ключевым словам внутреннего размера и обратно с помощью interpolate-size

Поддержка браузера

  • Хром: 129.
  • Край: не поддерживается.
  • Firefox: не поддерживается.
  • Сафари: не поддерживается.

Свойство CSS interpolate-size позволяет контролировать, следует ли разрешать анимацию и переходы ключевых слов внутреннего размера CSS или нет.

Его значение по умолчанию numeric-only , что не позволяет интерполяцию. При настройке свойстваallow allow-keywords вы соглашаетесь на интерполяцию от длины до ключевых слов внутреннего размера CSS в тех случаях, когда браузер может анимировать эти ключевые слова.

Согласно спецификации :

  • numeric-only : <intrinsic-size-keyword> не может быть интерполировано.
  • allow-keywords : два значения могут быть интерполированы, если одно из них — <intrinsic-size-keyword> , а другое — <length-percentage> . […]

Поскольку свойство interpolate-size является наследуемым, вы можете объявить его в :root , чтобы обеспечить переход к ключевым словам внутреннего размера и обратно для всего документа. Это рекомендуемый подход.

/* Opt-in the whole page to interpolate sizes to/from keywords */
:root {
    interpolate-size: allow-keywords; /* 👈 */
}

В следующей демонстрации это правило добавлено в код. В результате анимация по width: auto и обратно работает нормально (в браузерах с поддержкой):

Ограничьте охват подписки, сузив селектор.

Если вы хотите ограничить возможность allow-keywords только поддеревом вашего документа, настройте селектор с :root только на тот элемент, на который вы хотите ориентироваться. Например, если <header> вашей страницы несовместим с этими типами переходов, вы можете ограничить согласие только элементом <main> и его потомками следующим образом:

main { /* 👈 Scope the opt-in to only <main> and its descendants */
    interpolate-size: allow-keywords;
}

Почему бы не разрешить анимацию изменения размера ключевых слов по умолчанию?

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

Возможность включения этого поведения была исследована во время разработки этой функции. Рабочая группа обнаружила, что включение этого параметра по умолчанию не обеспечивает обратной совместимости, поскольку многие таблицы стилей предполагают, что ключевые слова внутреннего размера (такие как auto или min-content ) не могут быть анимированы. Подробности вы можете найти в этом комментарии к соответствующему вопросу Рабочей группы CSS .

Таким образом, собственность является добровольной. Благодаря свойству наследования выбор всего документа — это просто объявление interpolate-size: allow-sizes в :root как подробно описано ранее.

Анимация для ключевых слов внутреннего размера и обратно с помощью calc-size()

Поддержка браузера

  • Хром: 129.
  • Край: 129.
  • Firefox: не поддерживается.
  • Сафари: не поддерживается.

Другой способ включить интерполяцию к ключевым словам внутреннего размера и обратно — использовать функцию calc-size() . Это позволяет выполнять математические вычисления с внутренними размерами безопасным и четко определенным способом.

Функция принимает два аргумента по порядку:

  • Базис Calc-size , который может быть <intrinsic-size-keyword> , но также и вложенным calc-size() .
  • Расчет расчетного размера , позволяющий выполнять расчеты на основе расчетного размера. Чтобы обратиться к базе расчета размера, используйте ключевое слово size .

Вот несколько примеров:

width: calc-size(auto, size);        // = the auto width, unaltered
width: calc-size(min-content, size); // = the min-content width, unaltered

Если добавить calc-size() в исходную демонстрацию, код будет выглядеть следующим образом:

nav a {
    width: 80px;
    overflow-x: clip;
    transition: width 0.35s ease;

    &:hover,
    &:focus-visible {
        width: calc-size(max-content, size); /* 👈 */
    }
}

Визуально результат точно такой же, как и при использовании interpolate-size . Итак, в этом конкретном случае вам следует использовать interpolate-size .

В чем calc-size() действительно хорош, так это в его способности выполнять вычисления, чего нельзя сделать с помощью interpolate-size :

width: calc-size(auto, size - 10px); // = The auto width minus 10 pixels
width: calc-size(min-content, size + 1rem); // = The min-content width plus 1rem
width: calc-size(max-content, size * .5);   // = Half the max-content width

Например, если вы хотите, чтобы все абзацы на странице имели размер, кратный 50px , вы можете использовать следующее:

p {
    width: calc-size(fit-content, round(up, size, 50px));
    height: calc-size(auto, round(up, size, 50px));
}

calc-size() также позволяет вам интерполировать между двумя calc-size() , когда обе их базы Calc-размера идентичны. Этого тоже нельзя достичь с помощью interpolate-size .

#element {
    width: min-content; /* 👈 */
    transition: width 0.35s ease;

    &:hover {
        width: calc-size(min-content, size + 10px); /* 👈 */
    }
}

Почему бы не разрешить <intrinsic-size-keyword> в calc() ?

Вопрос, который обычно возникает при использовании calc-size() заключается в том, почему рабочая группа CSS не настроила функцию calc() для поддержки внутренних ключевых слов определения размера.

Одна из причин этого заключается в том, что вам не разрешено смешивать и сопоставлять ключевые слова внутреннего размера при выполнении вычислений. Например, у вас может возникнуть соблазн написать calc(max-content - min-content) , который выглядит корректным, но на самом деле это не так. calc-size() обеспечивает корректность, поскольку он, в отличие от calc() , принимает только одно единственное <intrinsic-size-keyword> в качестве первого аргумента.

Другая причина — контекстная осведомленность. Некоторые алгоритмы макета имеют особое поведение для определенных ключевых слов внутреннего размера. calc-size() явно определен для представления внутреннего размера, а не <length> . Благодаря этому эти алгоритмы могут обрабатывать calc-size(<intrinsic-size-keyword>, …) как <intrinsic-size-keyword> , сохраняя свое особое поведение для этого ключевого слова.

Какой подход использовать?

В большинстве случаев объявляйте interpolate-size: allow-keywords в :root . Это самый простой способ включить анимацию для ключевых слов внутреннего размера и обратно, поскольку по сути это однострочный вариант.

/* Opt-in the whole page to animating to/from intrinsic sizing keywords */
:root {
    interpolate-size: allow-keywords; /* 👈 */
}

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

Если вам нужен более детальный контроль над чем-то, например, выполнением вычислений, или вы хотите использовать поведение, которое может выполнять только calc-size() , вы можете прибегнуть к использованию calc-size() .

#specific-element {
    width: 50px;

    &:hover {
        width: calc-size(fit-content, size + 1em); /* 👈 Only calc-size() can do this */
    }
}

Однако использование calc-size() в вашем коде потребует включения резервных вариантов для браузеров, которые не поддерживают calc-size() . Например, добавление дополнительных объявлений размера или возврат к обнаружению функций с помощью @supports .

width: fit-content;
width: calc-size(fit-content, size + 1em);
       /* 👆 Browsers with no calc-size() support will ignore this second declaration,
             and therefore fall back to the one on the line before it. */

Больше демонстраций

Вот еще несколько демонстраций, которые используют interpolate-size: allow-keywords в своих интересах.

Уведомления

Следующая демонстрация является ответвлением этой демонстрации @starting-style . Код был скорректирован, чтобы можно было добавлять элементы разной высоты.

Для этого вся страница использует интерполяцию ключевых слов по размеру, а height каждого элемента .item устанавливается на auto . В остальном код точно такой же, как и до разветвления.

:root {
    interpolate-size: allow-keywords; /* 👈 */
}

.item {
    height: auto; /* 👈 */

    @starting-style {
        height: 0px;
    }
}

Анимируйте элемент <details>

Типичный вариант использования этого типа интерполяции — анимация виджета раскрытия или эксклюзивного аккордеона при его открытии. В HTML для этого используется элемент <details> .

С помощью interpolate-size: allow-keywords вы можете продвинуться довольно далеко:

@supports (interpolate-size: allow-keywords) {
    :root {
        interpolate-size: allow-keywords;
    }
    
    details {
        transition: height 0.5s ease;
        height: 2.5rem;
        
        &[open] {
            height: auto;
            overflow: clip; /* Clip off contents while animating */
        }
    }
}

Однако, как вы можете видеть, анимация запускается только тогда, когда открывается виджет раскрытия. Чтобы удовлетворить эту потребность, Chrome работает над псевдо- ::details-content , который появится в Chrome позднее в этом году (о нем будет рассказано в следующем посте). Комбинируя interpolate-size: allow-keywords и ::details-content , вы можете получить анимацию в обоих направлениях: