[Перевод] Путь к потрясающему CSS Easing с помощью новой функции linear()

Эта статья — перевод оригинальной статьи «The Path To Awesome CSS Easing With The linear () Function».

Также я веду телеграм канал «Frontend по‑флотски», где рассказываю про интересные вещи из мира разработки интерфейсов.

Вступление

Перефразируя поговорку, которая всегда была со мной: «Лучшая анимация — это та, которая остается в тени». Одна из самых важных концепций моушн дизайна в вебе — сделать так, чтобы моушн «чувствовался правильно». В то же время CSS был довольно ограничен в создании анимации и переходов, которые выглядят естественно и ненавязчиво для пользователя.

К счастью, ситуация меняется. Сегодня мы рассмотрим новые возможности анимации, появившиеся в CSS. В частности, я хочу продемонстрировать супер возможности linear() — новой функции для анимации, которая в настоящее время определена в спецификации CSS Easing Level 2 в редакции Editor’s Draft. Вместе мы изучим ее способность создавать кастомные кривые анимации, которые приводят к естественному движению пользовательского интерфейса.

Тот факт, что linear() находится в статусе Editor’s Draft, означает, что мы погружаемся в нечто, что еще только формируется и может измениться к тому времени, когда оно достигнет статуса Candidate Recommendation. Как вы можете себе представить, это означает, что на данный момент linear() имеет ограниченную поддержку. Однако она поддерживается в Chrome и Firefox, так что имейте это в виду, когда мы начнем демонстрировать некоторые примеры.

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

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

Текущее состояние Easing в CSS

Мы определяем CSS easing с помощью свойств animation-timing-function или transition-timing-function, в зависимости от того, работаем ли мы с анимацией или переходом соответственно.

Но до недавнего времени CSS ограничивал нас следующими easing функциями:

  • linear,

  • steps,

  • ease,

  • ease-in,

  • ease-out,

  • ease-in-out,

  • cubic-bezier().

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

https://codepen.io/smashingmag/pen/PoXmRJQ

Функция cubic-bezier () традиционно обеспечивает наибольшую гибкость при создании анимации. Сайт cubic-bezier.com — отличный ресурс для создания собственных функций. В противном случае, определение точных значений кривых может оказаться сложной задачей.

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

Визуализация кривых

Мы можем визуализировать различные кривые с помощью графика. На сайте easings.net хорошо представлены варианты, которые можно использовать с функцией cubic-bezier ().

0d3b0877928f7a0481c28632f44070b4.png

Кривые также можно просмотреть в Chromium DevTools, что позволяет просмотреть любую кривую, примененную к переходу или анимации.

887ecd50b4619f90c2fe26a73ece786c.png

Получение «большего» с помощью linear ()

Но что, если вам нужно нечто большее, чем то, что есть в наличии? Например, как насчет отскока? Или пружина? Это те типы функций, которых мы не можем достичь с помощью кривой cubic-bezier ().

Именно здесь на помощь приходит новая функция linear (), разработанная Джейком Арчибальдом и определенная в спецификации CSS Easing Level 2, которая в настоящее время находится в стадии Editor’s Draft. MDN хорошо описывает ее:

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

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

Например, анимация для отскока может выглядеть следующим образом:

:root {
  --bounce-easing: linear(
    0, 0.004, 0.016, 0.035, 0.063, 0.098, 0.141 13.6%, 0.25, 0.391, 0.563, 0.765,
    1, 0.891 40.9%, 0.848, 0.813, 0.785, 0.766, 0.754, 0.75, 0.754, 0.766, 0.785,
    0.813, 0.848, 0.891 68.2%, 1 72.7%, 0.973, 0.953, 0.941, 0.938, 0.941, 0.953,
    0.973, 1, 0.988, 0.984, 0.988, 1
  );
}

Вот как это выглядит в действии: https://codepen.io/smashingmag/pen/KKbmeeQ

Напоминаем, что поддержка браузеров ограничена Chrome и Firefox, поэтому обязательно посмотрите демо в одном из этих браузеров. На данный момент мы ждем только Safari, так что мы почти у цели!

«Я не понимаю как это работает».

Этот пример со выглядит как множество чисел, взятых прямо из воздуха, не так ли? Что касается сложности, то на первый взгляд мы имеем дело с чем-то таким же страшным, как и cubic-bezier(). Хорошо то, что после определения простоты вам вряд ли придется обращаться к ней снова… по крайней мере, какое-то время. Это практически вещь типа «задал и забыл».

Но как мы вообще получаем числа? Джейк, умный человек, создавший linear(), собрал онлайн-генератор, который делает всю тяжелую работу за нас. На самом деле, я получил значения для демонстрации отскока прямо из удобного инструмента Джейка. Вот ссылка на результат.

148cfceded2fcdb924966d499f502cd8.png

К чему все это приведет?

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

37730831fbbaa9e66fe0226259a47dd5.png

Как только я услышал о функции linear(), мои мысли сразу же вернулись к вопросу: «Как я могу преобразовать анимации GreenSock в CSS?». Представьте, как здорово было бы иметь доступ к популярному набору eases, которые можно использовать прямо в CSS, не обращаясь к JavaScript.

Визуализатор GreenSock принимает JavaScript или путь SVG. Поэтому первой моей мыслью было открыть DevTools, взять SVG-пути из визуализатора и вставить их в инструмент. Однако я столкнулся с препятствием, поскольку мне нужно было уменьшить масштаб координат пути для viewBox 0 0 1 1. Визуализатор GreenSock имеет viewBox, установленный на 0 0 500 500. Я написал функцию для преобразования координат и реверсирования пути, чтобы он шел в правильном направлении. Затем я обратился к Джейку с некоторыми вопросами о генераторе. Код доступен на GitHub.

В моей голове я думал, что SVG имеет смысл. Но потом я создал вариант, который не работал. Поэтому я снова связался с Джейком, и мы оба решили, что проблема заключается в ошибке утилиты.

Тогда Джейк спросил: «Почему вам нужно использовать SVG?». Его вопрос был точным! JavaScript-вход ожидает easing функцию. Easing функция сопоставляет время со значением прогресса. И мы можем получить эту функции прямо из GreenSock и передать их генератору. Джейку удалось выудить функцию из репозитория GreenSock на GitHub и создать ту анимацию, которая мне изначально была нужна.

Генерация GSAP Eases для CSS

Теперь, когда я рассказал вам о контексте, у нас есть все части головоломки, необходимые для создания чего-то, что может преобразовать GSAP easing в код CSS.

Сначала мы извлечем части из инструмента Джейка для генерации linear() в скрипт. Идея заключается в том, чтобы перебрать набор ключей и сгенерировать блок CSS с linear() easing. В GreenSock есть замечательный метод-утилита под названием parseEase. Он принимает строку и возвращает easing функцию. Принимаемые строки — это easing функции GreenSock.

const ease = gsap.parseEase('power1.in')
ease(0.5) // === 0.25

Поскольку в этом цикле объект имеет различные easing функции, мы можем передать их в извлеченный из инструмента код. Мы модифицируем извлеченный код в соответствии с нашими потребностями.

const easings = ''
const simplified = 0.0025
const rounded = 4
const EASES = {
  'power-1--out': gsap.parseEase('power1.out')
  // Other eases
}
// Loop over the easing keys and generate results.
for (const key of Object.keys(EASES)) {
  // Pass the easing function through the linear-generator code.
  const result = processEase(key, EASES[key])
  const optimised = useOptimizedPoints(result.points, simplified, rounded)
  const linear = useLinearSyntax(optimised, rounded)
  const output = useFriendlyLinearCode(linear, result.name, 0)
  easings += output
}
// Generate an output CSS string.
let outputStart = ':root {'
let outputEnd = '}' 
let styles = `
  ${outputStart}
  ${easings}
  ${outputEnd}
`
// Write it to the body.
document.body.innerHTML = styles

Функции, которые мы извлекли из линейного генератора, делают разные вещи:

  • processEase
    Это модифицированная версия processScriptData. Она принимает easing функции и возвращает точки для нашего графика.

  • useOptimizedPoints
    Это оптимизирует точки на основе упрощенных и округленных значений. Именно здесь я узнал об алгоритме Дугласа Пикера от Джейка.

  • useLinearSyntax
    Эта функция берет оптимизированные точки и возвращает значения для функции linear().

  • useFriendlyLinearCode
    Эта функция принимает линейные значения и возвращает CSS-строку, которую мы можем использовать с именем пользовательского свойства ease.

Стоит отметить, что я старался не трогать их слишком часто. Но стоит покопаться и поставить точку останова или вывести console.info в разных местах, чтобы понять, как все работает.

После запуска результат дает нам переменные CSS, содержащие функции и значения linear() easing. В следующем примере показаны анимации упругости и отскока.

:root {
  --elastic-in: linear( 0, 0.0019 13.34%, -0.0056 27.76%, -0.0012 31.86%, 0.0147 39.29%, 0.0161 42.46%, 0.0039 46.74%, -0.0416 54.3%, -0.046 57.29%, -0.0357, -0.0122 61.67%, 0.1176 69.29%, 0.1302 70.79%, 0.1306 72.16%, 0.1088 74.09%, 0.059 75.99%, -0.0317 78.19%, -0.3151 83.8%, -0.3643 85.52%, -0.3726, -0.3705 87.06%, -0.3463, -0.2959 89.3%, -0.1144 91.51%, 0.7822 97.9%, 1 );
  --elastic-out: linear( 0, 0.2178 2.1%, 1.1144 8.49%, 1.2959 10.7%, 1.3463 11.81%, 1.3705 12.94%, 1.3726, 1.3643 14.48%, 1.3151 16.2%, 1.0317 21.81%, 0.941 24.01%, 0.8912 25.91%, 0.8694 27.84%, 0.8698 29.21%, 0.8824 30.71%, 1.0122 38.33%, 1.0357, 1.046 42.71%, 1.0416 45.7%, 0.9961 53.26%, 0.9839 57.54%, 0.9853 60.71%, 1.0012 68.14%, 1.0056 72.24%, 0.9981 86.66%, 1 );
  --elastic-in-out: linear( 0, -0.0028 13.88%, 0.0081 21.23%, 0.002 23.37%, -0.0208 27.14%, -0.023 28.64%, -0.0178, -0.0061 30.83%, 0.0588 34.64%, 0.0651 35.39%, 0.0653 36.07%, 0.0514, 0.0184 38.3%, -0.1687 42.21%, -0.1857 43.04%, -0.181 43.8%, -0.1297 44.93%, -0.0201 46.08%, 1.0518 54.2%, 1.1471, 1.1853 56.48%, 1.1821 57.25%, 1.1573 58.11%, 0.9709 62%, 0.9458, 0.9347 63.92%, 0.9349 64.61%, 0.9412 65.36%, 1.0061 69.17%, 1.0178, 1.023 71.36%, 1.0208 72.86%, 0.998 76.63%, 0.9919 78.77%, 1.0028 86.12%, 1 );
    --bounce-in: linear( 0, 0.0117, 0.0156, 0.0117, 0, 0.0273, 0.0468, 0.0586, 0.0625, 0.0586, 0.0468, 0.0273, 0 27.27%, 0.1093, 0.1875 36.36%, 0.2148, 0.2343, 0.2461, 0.25, 0.2461, 0.2344, 0.2148 52.28%, 0.1875 54.55%, 0.1095, 0, 0.2341, 0.4375, 0.6092, 0.75, 0.8593, 0.9375 90.91%, 0.9648, 0.9843, 0.9961, 1 );
  --bounce-out: linear( 0, 0.0039, 0.0157, 0.0352, 0.0625 9.09%, 0.1407, 0.25, 0.3908, 0.5625, 0.7654, 1, 0.8907, 0.8125 45.45%, 0.7852, 0.7657, 0.7539, 0.75, 0.7539, 0.7657, 0.7852, 0.8125 63.64%, 0.8905, 1 72.73%, 0.9727, 0.9532, 0.9414, 0.9375, 0.9414, 0.9531, 0.9726, 1, 0.9883, 0.9844, 0.9883, 1 );
  --bounce-in-out: linear( 0, 0.0078, 0, 0.0235, 0.0313, 0.0235, 0.0001 13.63%, 0.0549 15.92%, 0.0938, 0.1172, 0.125, 0.1172, 0.0939 27.26%, 0.0554 29.51%, 0.0003 31.82%, 0.2192, 0.3751 40.91%, 0.4332, 0.4734 45.8%, 0.4947 48.12%, 0.5027 51.35%, 0.5153 53.19%, 0.5437, 0.5868 57.58%, 0.6579, 0.7504 62.87%, 0.9999 68.19%, 0.9453, 0.9061, 0.8828, 0.875, 0.8828, 0.9063, 0.9451 84.08%, 0.9999 86.37%, 0.9765, 0.9688, 0.9765, 1, 0.9922, 1 );
}

Мы можем настроить этот вывод по своему усмотрению, используя различные ключи или точность. Самое замечательное, что теперь мы можем добавить эти GreenSock easing в CSS!

Как получить свой собственный CSS linear () Ease

Вот небольшой инструмент, который я создал. Он позволяет выбрать нужный тип анимации, применить к ней linear () easingи задать скорость. После этого переверните карточку, чтобы просмотреть и скопировать сгенерированный код.

https://codepen.io/smashingmag/pen/zYywLXB

В случаях, когда linear () не поддерживается браузером, мы можем использовать запасное значение для удобства с помощью @supports:

:root {
  --ease: ease-in-out;
}
@supports(animation-timing-function: linear(0, 1)) {
  :root {
    --ease: var(--bounce-easing);
  }
}

И просто для развлечения, вот демо, которое принимает на вход строку GreenSock ease и выдает вам функцию linear(). Попробуйте что-нибудь вроде elastic.out(1, 0.1) и посмотрите, что получится!

https://codepen.io/smashingmag/pen/RwEVBmM

Бонус: Линейные функции для Tailwind #

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

/** @type {import('tailwindcss').Config} */
const plugin = require('tailwindcss/plugin')
const EASES = {
  "power1-in": "linear( 0, 0.0039, 0.0156, 0.0352, 0.0625, 0.0977, 0.1407, 0.1914, 0.2499, 0.3164, 0.3906 62.5%, 0.5625, 0.7656, 1 )",
  /* Other eases */
}
const twease = plugin(
  function ({addUtilities, theme, e}) {
    const values = theme('transitionTimingFunction')
    var utilities = Object.entries(values).map(([key, value]) => {
      return {
        [`.${e(`animation-timing-${key}`)}`]: {animationTimingFunction: `${value}`},
      }
    })
    addUtilities(utilities)
  }
)
module.exports = {
  theme: {
    extend: {
      transitionTimingFunction: {
        ...EASES,
      }
    },
  },
  plugins: [twease],
}

Я собрал кое-что в Tailwind Play, чтобы вы могли увидеть это в действии и немного поэкспериментировать. Это даст вам такие классы, как animation-timing-bounce-out и ease-bounce-out.

Заключение

CSS традиционно предоставлял лишь ограниченный контроль над временем анимации и переходов. Единственным способом добиться желаемого поведения было обращение к решениям JavaScript. Скоро это изменится, благодаря супер возможностям новой функции тайминга linear(), которая определена в проекте спецификации CSS Easing Level 2.

© Habrahabr.ru