Pull to refresh

Анимации CSS, основанные на времени

Level of difficultyEasy
Reading time4 min
Views4.8K
Original author: Anton Fuchs

Демонстрация анимаций

В моем предыдущем посте Time Uniform For CSS Animation я рассказал о способе создания CSS-анимации с использованием тиков вместо ключевых кадров. Он был ограничен в применении, поскольку в CSS отсутствовала возможность выполнять сложные математические вычисления.

После долгих лет ожидания в CSS теперь поддерживается достаточное количество математических функций, в частности, mod(), round() и тригонометрические функции. Пришло время вернуться к анимации, основанной на времени, надеюсь, на этот раз она будет более полезной.

Основная идея

Использование времени для анимации очень часто встречается в шейдерных программах и различных других местах. CSS не может запустить таймер, как это делает JavaScript, но сегодня можно определить пользовательскую переменную с помощью CSS Houdini API для отслеживания времени в миллисекундах.

@property --t {
  syntax: "<integer>";
  initial-value: 0;
  inherits: true
}
@keyframes tick {
  from { --t: 0 }
  to   { --t: 86400000 }
}
:root {
  animation: tick 86400000ms linear infinite
}

За каждую миллисекунду переменная --t увеличивается на 1, что составляет 1000 за одну секунду. Есть хитрость в том, чтобы задать переменную с помощью функции counter().

Другие значения, основанные на --t, будут меняться вместе с ним. Так мы получаем эффект анимации.

div {
  /* 1 оборот в секунду */
  rotate: calc(var(--t) * .001turn);
}

Управление частотой кадров

Для плавной анимации достаточно поддерживать частоту обновления на уровне 60 кадров в секунду (FPS). Браузеры часто оптимизируют рендеринг, поэтому не будет никаких проблем, если частота будет выше 60 FPS. Но при необходимости можно вручную регулировать частоту кадров с помощью функции step().

:root {
  animation: tick;
  animation-duration: 86400000ms;
  animation-iteration-count: infinite;

  /* 8 fps */
  animation-timing-function: step(calc(86400000/(1000/8)));

  /* 24 fps */
  animation-timing-function: step(calc(86400000/(1000/24)));

  /* 60 fps */
  animation-timing-function: step(calc(86400000/(1000/60)));
}

Время преобразования

Значение параметра --t постоянно увеличивается в одном направлении. Это нормально для angle, значения, которого, превышает 360deg, однако не все свойства CSS обрабатывают свои значения как циклические.

Допустим, я хочу анимировать движение квадрата слева направо, если смещение translate связано с --t, оно будет постоянно увеличиваться без остановки.

translate: calc(var(--t) * .001px);

min()

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

translate: min(270px, calc(var(--t) * .5px));

Чтобы точно контролировать продолжительность анимации, мы можем ограничить значение переменной --t.

/* 270px за 3 секунды */
translate: calc(min(3000, var(--t)) * (270px / 3000));

mod()

После того как квадрат переместился вправо, можно запустить смещение с самого начала. Теперь у нас есть функция mod() для достижения этой цели.

translate: calc(mod(var(--t)/4, 270) * 1px);

sin()

Или совершать движения взад-вперед.

translate: calc(sin(mod(var(--t)/135, 270)) * 135px);

Пользовательская функция упрощения

Мы можем создавать пользовательские функции упрощения, используя математические функции и переменную --t, что может быть недостижимо с помощью cubic-bezier().

ease-out-cubic

На начальном этапе необходимо привязать значение --t между 0 и 1.

/* от 0 к 1 за 1 секунду */
--t01: calc(min(1000, var(--t)) / 1000);

/* 1 - pow(1 - t, 3) */
--ease-out-cubic: calc(
  1 - pow(1 - var(--t01), 3)
);

translate: calc(var(--ease-out-cubic) * 270px);

ease-out-elastic

/* от 0 к 1 за 1 секунду */
--t01: calc(min(1000, var(--t)) / 1000);

/* pow(2, -10t) * sin((10t - .75) * 2/3 * PI) + 1 */
--ease-out-elastic: calc(
  pow(2, -10 * var(--t01)) *
  sin((var(--t01) * 10 - .75) * 2/3 * PI) + 1
);

translate: calc(var(--ease-out-elastic) * 270px);

Эксперименты с CSS Doodle

По мере усложнения выражений var() и calc() делают код менее читабельным. Поэтому я добавил функцию @t для представления переменной --t. Последняя версия css-doodle также принимает простые математические выражения непосредственно внутри аргументов.

/* rotate: calc(mod(var(--t) / 1000, 10) * 5deg); */
rotate: @t(/1000, %10, *5deg);

Без написания ключевых кадров код получается коротким.

@grid: 20x1 / 280x 60px;
@gap: 1px;
@size: 100% 20%;
background: #000;
margin: auto;
translate: 0 calc(20px * sin(4*@t(/20, +@i(*6), %360deg)));

Кроме того, можно быстро опробовать новые параметры.

translate: 0 calc(20px * sin(3*@t(/50, *@i(*2), %360deg)));

Функции @T и @TS

В дополнение к функции @t, функция @T в верхнем регистре обозначает еще один отсчет времени с начала дня. Функция @TS - это сокращение от @t(/1000), которая отслеживает время в секундах.

Вот часы, реализованные в css-doodle. (CodePen link)

/* секунда */
rotate: @TS(*6, %360deg);

/* минута */
rotate: @TS(/60, *6, %360deg);

/* час */
rotate: @TS(/60, /12, *6, %360deg);

round()

Как сделать прыгающее движение для секундной стрелки? Прямой подход - использовать функцию round(), где третий параметр задает интервал округления. В контексте часов каждый шаг равен 360 / 60 = 6deg.

rotate: round(down, @TS(*6, %360deg), 6deg);

Больше примеров

Анимированные цвета и их расположение. (CodePen link).

@grid: 100x1 / 100% auto (4/3) / #10153e;
@size: @rn(1vmin, 5vmin, 10);
margin: auto;
border-radius: 50%;

background: @p(
  hsl(@t(/10, +@i(*2), %360), 90%, 80%)
);
box-shadow: @m5(
  @r(±23vmin) @r(±23vmin) @r(2vmin) @r(-40px) @p
);
translate: @M2(
  calc(100px * tan(6*cos(@t(/10, +@i(*10), /6, %360deg))))
);

Анимированный цвет текста. (CodePen link).

@grid: 32 / 400px / #000 ^.95;
@content: @pn([a-z0-9.]);

--x: abs(@dx/@X);
--y: abs(@dy/@Y);

line-height: 0;
font-size: 12px;
font-family: monospace;
color: hsl($(480x), 100%, calc(tan($x/tan($y*4)*50 - @t(/400))*100%));

Заключение

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

Если вас заинтересовала статья, приглашаем в наш телеграм-канал WebWeavers, в котором мы изучаем frontend и дизайн. В нём мы выкладываем интересные и поучительные посты.

Tags:
Hubs:
Total votes 15: ↑15 and ↓0+16
Comments0

Articles