calc-mix() и progress() – что за функции и в чём разница?

• css

Вступление

progress()
138
26

Думаю, те, кому интересны всяческие новинки в CSS-мире от браузеров, могли слышать про progress(). Как-никак есть статья на MDN и 2 стабильных браузерных движка с реализацией. А вот что касается calc-mix(), то это более редкий зверь. Реализация пока есть только в Safari Technical Preview 247+, и пока только спека.

А ещё эти две функции могут… Смутить? И то и то про смешивание значений, и то и то принимает несколько значений, и то и то дружит с разными единицами.

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

progress()

Начнём с уже существующей и более понятной функции. progress() позволяет посчитать значение без единиц измерения, которое ответит на вопрос, “на какой стадии прогресса от А до Б мы находимся”:

.block {
    --progress: progress(current, from, to);
    ...
    --progress: progress(100vw, 100px, 200px);
}

В качестве аргументов можно использовать значения без единиц измерения, значения “длинны” и проценты. Использовать другие единицы нельзя.

Можно подставлять функции внутрь, так и использовать progress() внутри других функций, например, в rgb().

Функция progress() возвращает значение от 0 до 1. По сути, такого же эффекта можно добиться, если просто посчитать

.block {
    --progress: progress(current, from, to);
    /* -> */
    --progress: clamp(0, calc((current - from) / (to - from)), 1);
}

Конечно, последняя строка работает только в браузерах, которые умеют в “типизированную арифметику”.

Для получения чисел без единиц измерения во всех браузерах сейчас можно использовать хак с tan(atan2(...)), а в браузерах из списка выше достаточно просто деления:

.block {
    --unitless: calc(300px / 1px);
}

progress() – ещё один способ получить числовое значение “без единиц измерения”.

calc-mix()

У calc-mix() история интереснее. В текущем Working Draft функция принимает 3 значения и похожа на обратную к progress() – просто смешивает два значения в пропорции.

А вот в Editor Draft функция резко меняется, и начинает принимать любое количество значений, каждое из которых можно снабдить процентами. Прямо как color-mix(), который недавно прокачали и теперь он может принимать больше двух цветов.

В Safari реализовали именно второй вариант:

.block {
    /* 150px */
    width: calc-mix(100px 50%, 200px 50%);
}

В общем, вполне себе “пиксельная версия” color-mix(). В случае, если сумма больше 100% – проценты нормализуются. Проценты можно и опустить:

.block {
    /* 150px */
    width: calc-mix(100px, 200px);
}

Если же одно значение записано без процентов, а второе – с процентами, то выражение будет задавать интервал от первого до второго при использовании 0-100%:

.block {
    /* 100px */
    width: calc-mix(100px, 200px 0%);
    /* 150px */
    width: calc-mix(100px, 200px 50%);
    /* 200px */
    width: calc-mix(100px, 200px 100%);
}

Также в текущей версии нельзя использовать проценты за пределами интервала 0-100 (а это было бы полезно для анимации “пружины”).

По сути, calc-mix() – всего лишь синтаксический сахар, и того же самого можно написать через обычный calc().

Примеры

В доке есть пример с элементом, когда цвет высчитывается исходя из текущей ширины элемента:

.block {
    --custom-minwidth: 320px;
    --custom-maxwidth: 1200px;
    --progress: progress(
        var(--container-width),
        var(--custom-minwidth),
        var(--custom-maxwidth)
    );
 
    background-color: rgb(
        calc(
            255 *
            var(--progress)
        )
        calc(
            255 *
            var(--progress)
        )
        255 / 0.5
    )
}

--container-width выставляется в примере скриптом, но ничего не мешает использовать вместо этого какой-нибудь 100vw. Как визуальный залипательный элемент на промо-страничке – почему нет.

Аналогичным способом можно сделать и без скрипта, используя container-query:

.container {
    container-type: size;
 
    --container-width: 100cqw;
 
    ...
}

Благо, поддержка cqw выглядит всё лучше и лучше:

Другой способ использования progress() и calc-mix(). Есть классический пример с clamp():

p {
    font-size: clamp(1rem, 2.5vw, 2rem);
}

Если же хочется свободного поведения, то можно использовать новые функции:

p {
    --progress: progress(100vw, 320px, 1200px);
    font-size: calc-mix(14px, 18px var(--progress));
}

Из плюсов мы получаем более тонкую настройку (выставили минимум и максимум ширины) и более явные значения (в примере с clamp() нужно ещё понять, когда будет достигаться 1rem, а когда нет).

Ещё одна возможность – использование sibling-index() в вычислениях. Это позволит задавать отступ лесенкой, поворот по спирали и подобные вещи.

Итого

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

Ну а самый главный итог для меня – очень жду “типизированной арифметики” во всех браузерах. А до тех пор нужно очень внимательно тестировать сложные calc(), особенно в Файерфоксе.

Обсудить в Telegram

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

  • Автоматические размеры полей с field-sizing
  • text-fit: свойство, которое дождались
  • Декораторы отступов, или свойства, которые у нас уже давно были
  • Ищем баланс с помощью flex-wrap: balance
  • Калькулятор фолбека для corner-shape