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

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

Комментарии (12)


  1. azTotMD
    23.05.2024 10:29

    не знал о такой технологии, а чем это лучше гифок? И какие браузеры такое поддерживают?


    1. metter
      23.05.2024 10:29
      +2

      Предположу, что даст выигрыш при дизайне с большим кол-вом отдельных мелких анимаций. Получается, что можно всё запихнуть в один файл.


      1. azTotMD
        23.05.2024 10:29

        вроде когда много мелких анимаций, выгоднее как раз гифки. Допустим, карта из анимированных квадратиков. По мере открывания новых квадратиков - подгружаются новые гифки. Те которые игрок уже видел - кэшируются брауезром и догружать не надо. С другой стороны, какие-то редкие, которые есть шанс, что и не понадобятся во время игровой сессии не будут висеть в памяти.


        1. Fl0k1 Автор
          23.05.2024 10:29

          GIF-анимация состоит из серии кадров, упакованных в один файл. Этот формат поддерживает простую анимацию с ограниченным количеством цветов (до 256) и может включать прозрачные области.

          В анимации с GIF вы ограничены возможностями формата, включая скорость анимации и порядок кадров, заданные при создании GIF.


        1. Fl0k1 Автор
          23.05.2024 10:29

          Так же размер, если взять анимацию сундука из примера (128px/114px) в gif формате его размер будет равен ~22.8kb, а размер спрайт листа ~3.43kb, а это простая анимация на 15 кадров


    1. Fl0k1 Автор
      23.05.2024 10:29
      +1

      Этим можно управлять (останавливать, воспроизводить, хотя гифками тоже можно), и начинать с определенного кадра анимацию и т.д. В основном используется в играх где "файловая графика" (Герои 3 например), чтобы все изображения хранить в одном файле а потом уже многократно резать и использовать. Поддерживается в chrome 125 версии, safari 15.4 и firefox 118


      1. azTotMD
        23.05.2024 10:29
        +1

        спасибо!


      1. Fl0k1 Автор
        23.05.2024 10:29

        *тайловая графика


  1. krasilnik_k
    23.05.2024 10:29

    Поддерживается в chrome 125 версии, safari 15.4 и firefox 118


    1. Fl0k1 Автор
      23.05.2024 10:29

      Если точнее то @property ещё не завезли в firefox, а вот функции mod и round со 118 уже есть


  1. Mingun
    23.05.2024 10:29

    Пока что кажется, что весь сыр-бор только из-за того, что чем-то не нравятся «однострочные» атласы. Нет, я не спорю, наличие функций round() и mod() в CSS может оказаться полезным, но вот пример их применения какой-то… надуманный.


    1. Fl0k1 Автор
      23.05.2024 10:29
      +1

      В моем профиле на codepen добавляю ещё примеры, с анимацией текста, маски для эффекта загрузки картинки. Как соберу достаточно разных вариаций, опишу ещё примеры