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