Введение. Что это такое?
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
Комментарии (12)
krasilnik_k
23.05.2024 10:29Поддерживается в chrome 125 версии, safari 15.4 и firefox 118
Fl0k1 Автор
23.05.2024 10:29Если точнее то
@property
ещё не завезли в firefox, а вот функцииmod
иround
со 118 уже есть
Mingun
23.05.2024 10:29Пока что кажется, что весь сыр-бор только из-за того, что чем-то не нравятся «однострочные» атласы. Нет, я не спорю, наличие функций
round()
иmod()
в CSS может оказаться полезным, но вот пример их применения какой-то… надуманный.Fl0k1 Автор
23.05.2024 10:29+1В моем профиле на codepen добавляю ещё примеры, с анимацией текста, маски для эффекта загрузки картинки. Как соберу достаточно разных вариаций, опишу ещё примеры
azTotMD
не знал о такой технологии, а чем это лучше гифок? И какие браузеры такое поддерживают?
metter
Предположу, что даст выигрыш при дизайне с большим кол-вом отдельных мелких анимаций. Получается, что можно всё запихнуть в один файл.
azTotMD
вроде когда много мелких анимаций, выгоднее как раз гифки. Допустим, карта из анимированных квадратиков. По мере открывания новых квадратиков - подгружаются новые гифки. Те которые игрок уже видел - кэшируются брауезром и догружать не надо. С другой стороны, какие-то редкие, которые есть шанс, что и не понадобятся во время игровой сессии не будут висеть в памяти.
Fl0k1 Автор
GIF-анимация состоит из серии кадров, упакованных в один файл. Этот формат поддерживает простую анимацию с ограниченным количеством цветов (до 256) и может включать прозрачные области.
В анимации с GIF вы ограничены возможностями формата, включая скорость анимации и порядок кадров, заданные при создании GIF.
Fl0k1 Автор
Так же размер, если взять анимацию сундука из примера (128px/114px) в gif формате его размер будет равен ~22.8kb, а размер спрайт листа ~3.43kb, а это простая анимация на 15 кадров
Fl0k1 Автор
Этим можно управлять (останавливать, воспроизводить, хотя гифками тоже можно), и начинать с определенного кадра анимацию и т.д. В основном используется в играх где "файловая графика" (Герои 3 например), чтобы все изображения хранить в одном файле а потом уже многократно резать и использовать. Поддерживается в chrome 125 версии, safari 15.4 и firefox 118
azTotMD
спасибо!
Fl0k1 Автор
*тайловая графика