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

В чем была сложность до появления 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 можно было наблюдать анимацию изображения. Например:

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

Что изменилось с выходом 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);
Результат

P.S.: Первый пост, на платформе. (Пока разбираюсь).
Демо с настройками анимации (правый верхний угол): https://codepen.io/Maseone/pen/oNRxxWX
