Как стать автором
Обновить

Новые функции CSS (mod, round) или как сделать анимированные Sprite Sheet без JS

Уровень сложностиСредний
Время на прочтение5 мин
Количество просмотров5.6K

Введение. Что это такое?

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

Что из себя представляет

Sprite Sheet — это композиция из множества различных изображений (обычно кадров анимации), объединенных в один файл. Это позволяет управлять анимацией персонажей или элементов интерфейса, изменяя позицию изображения в CSS.

Sprite Sheet сундуков в разных состояниях и направлениях
Sprite Sheet сундуков в разных состояниях и направлениях

В чем была сложность до появления round и mod в CSS?

Основная сложность заключалась в том чтобы автоматизировать процесс перехода по строкам. Например:

@keyframes sprite {
  from { background-position: 0px; }
  to { background-position: -8640px; }
}

Начальное Положение (from): Анимация начинается с background-position: 0px;. Это означает, что фоновое изображение начнет отображаться с начальной позиции, обычно это левый верхний угол изображения.

Конечное Положение (to): Анимация заканчивается при background-position: -8640px;. Это означает, что фоновое изображение будет сдвигаться по горизонтали на -8640 пикселей от начальной позиции к концу анимации. Такой сдвиг обычно используется для прохождения через различные кадры Sprite Sheet, где каждый кадр представляет различные этапы анимации.

Далее при помощи animation-timing-function: steps(x); где X это количество кадров мы могли производить смещение по кадрам. В зависимости от времени выполнения и частоты кадров в Sprite Sheet можно было наблюдать анимацию изображения. Например:

Animated mario by frames
Animated mario by frames

В данном примере выполнялась анимация только по оси X и приходилось вручную выставлять в @keyframes границы переходов по оси Y, либо нарезать изображение в inline формате.

Inline sprite sheet
Inline sprite sheet

Что изменилось с выходом Chrome 125?

Были добавлены новые функции:

  • CSS-функция round() возвращает округленное число на основе выбранной стратегии округления.

  • CSS-функция mod() возвращает модуль, оставшийся при делении первого параметра на второй параметр, аналогично оператору остатка в JavaScript (%). Модуль — это значение, оставшееся после деления одного операнда, делимого, на второй операнд, делитель. Оно всегда принимает знак делителя.

  • CSS-функция rem() возвращает остаток, оставшийся при делении первого параметра на второй параметр, аналогично оператору остатка JavaScript (%). Остаток — это значение, оставшееся после деления одного операнда (делимого) на второй операнд (делитель). Оно всегда принимает знак дивиденда.

Что это нам дает?

Теперь мы можем написать анимированные Sprite Sheet с переходами по X и Y координатам на чистом CSS! Давайте начнем с примера который выложен на Codepen. (Если кто то хочет, может сразу перейти по ссылке и сам разобраться что к чему)

Начнем!

Для начала нам нужно определить:

@property --sprite-fs {    
  syntax: "<integer>"; /* Описывает допустимый синтаксис свойства. */   
  initial-value: 0;  /* Устанавливает начальное значение свойства. */
  inherits: false;  /* Определяет наследования свойства */
}

@property - является частью API-интерфейсов CSS Houdini . Оно позволяет разработчикам явно определять свои свойства, позволяя проверять тип свойства, устанавливать значения по умолчанию и определять @property , может ли свойство наследовать значения или нет. В Chrome со 119 версии.

--sprite-fs - в данном случае является начальной точкой отсчета кадров (сейчас выставлен 0, но можно изменять и начинать анимацию с любого кадра)

Далее, необходимые обязательные переменные:

/* Количество колонок */
--sprite-c: 5;
/* Высота Sprite Sheet */
--sprite-h: 345;
/* Ширина Sprite Sheet */
--sprite-w: 640;
/* Количество кадров */
--sprite-f: 15;

Как вы уже догадались, далее идет несложная математика:

/* Считаем количество кадров (от 0 поэтому, 15 - 1) */
--sprite-fe: calc(var(--sprite-f) - 1);
/* Считаем количество строк с округлением до большего целого числа (15/5 = 3) */
--sprite-r: round(up, calc(var(--sprite-f) / var(--sprite-c)), 1);
/* Считаем высоту отдельного фрейма (345/3 = 115) */
--sprite-sh: calc(var(--sprite-h) / var(--sprite-r));
/* Данное свойство изменяемое, можно вводить любое значение чтобы изменять высоту спрайта при  отрисовке */
--sprite-th: 100; /* default: var(--sprite-sh) */
/* Aspect ratio для пропорционального изменения ширины (~0.869) */
--sprite-ar: calc(var(--sprite-th) / var(--sprite-sh));
/* Обновленный размер ширины и высоты Sprite Sheet (w: ~556.52, ~h:300) */
--sprite-uh: calc(var(--sprite-h) * var(--sprite-ar));
--sprite-uw: calc(var(--sprite-w) * var(--sprite-ar));
/* Считаем ширину отдельного фрейма (556.52/5 = 111.30) */
--sprite-tw: calc(var(--sprite-uw) / var(--sprite-c));

Далее задаем свойства нашего элемента:

/* Высота блока куда будет выводится анимация */
height: calc(1px * var(--sprite-th)); 
/* Ширина блока куда будет выводится анимация */
width: calc(1px * var(--sprite-tw));
/* Sprite Sheet */
background-image: var(--sprite-image);
/* Итоговый размер background с учетом aspect ratio */
background-size: calc(1px * var(--sprite-uw)) calc(1px * var(--sprite-uh));

А теперь то что стало возможным с введением round и mod:

/* Да, это всё еще CSS) */
/* Расчет текущей строки, определяем выходит ли наше изображение на пределы ширины Sprite Sheet */
/* Определяем шаг по X ((ширина спрайта * текущий кадр) / ширину спрайт листа) */
/* Округляем в меньшую сторону, находим позицию строки Y (0,1,2) умножаем на высоту спрайта для координат */
--row: calc(round(down, calc(calc(var(--sprite-tw) * var(--sprite-fs)) / var(--sprite-uw)), 1) * var(--sprite-th));
/* Расчет текущей колонки */
/* Остаток от деления по X (ширина спрайта * текущий кадр) / ширину спрайт листа */
/* Позволяет определить позицию по X. В нашем примере (0,1,2,3,4)) */
--col: mod(calc(var(--sprite-tw) * var(--sprite-fs)), var(--sprite-uw));

Переводим в px, выставляем background-size:

background-position: calc(-1px * var(--col)) calc(-1px * var(--row));
Первый кадр, выставлен (подложка для наглядности)
Первый кадр, выставлен (подложка для наглядности)

Анимируем

Тут все очень просто

@keyframes frame {
    to {
        --sprite-fs: var(--sprite-fe);
    }
}

Ранее мы задавали @property --sprite-fs, которое обозначало кадр начала анимации, теперь мы задаем to --sprite-fe (индекс последнего кадра) т.е. анимация будет выполняться от 0 до 14 (15 кадров).

Анимируем:

/* animation duration */
--sprite-as: 4s;
/* animation direction */
--sprite-ad: normal;
/* animation fill mode */
--sprite-af: none;
/* animation play state */
--sprite-ap: running;
/* animation iteration count */
--sprite-ai: infinite;
/* animation timing function */
--sprite-at: linear;

/* одинокий delay в 0s :) */
animation: frame var(--sprite-as) var(--sprite-at) 0s var(--sprite-ai) var(--sprite-ad) var(--sprite-af) var(--sprite-ap);

Результат

Пример анимации Sprite Sheet
Пример анимации Sprite Sheet

P.S.: Первый пост, на платформе. (Пока разбираюсь).

Демо с настройками анимации (правый верхний угол): https://codepen.io/Maseone/pen/oNRxxWX

Теги:
Хабы:
Всего голосов 6: ↑6 и ↓0+9
Комментарии14

Публикации

Истории

Ближайшие события

3 – 18 октября
Kokoc Hackathon 2024
Онлайн
10 – 11 октября
HR IT & Team Lead конференция «Битва за IT-таланты»
МоскваОнлайн
25 октября
Конференция по росту продуктов EGC’24
МоскваОнлайн
7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн