В моем предыдущем посте 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 и дизайн. В нём мы выкладываем интересные и поучительные посты.