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

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

Почему иконки стоят дорого при их маленьком размере

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

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

Третья составляющая это кеш: файл значков запрашивается повторно только когда изменился, и стратегия обновления решает, дёшево ли живёт страница. Отдельные значки инвалидируются точечно, но проверка каждого точечного изменения стоит запроса с условием, спрайт инвалидируется целиком, зато проверяется один раз, и баланс решается размером и частотой правок.

Четвёртая составляющая это отрисовка: сотня мелких декодированных картинок держит сотню записей в памяти, один спрайт держит одну. Составляющая скромная, но на страницах с сотнями повторяющихся значков она складывается в заметные строки профиля памяти, и экономика начинается раньше, чем кажется.

Классический лист спрайтов одна картинка и координаты в стилях

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

.icon {
  background-image: url("/assets/sheet.png");
  background-repeat: no-repeat;
}

.icon-home {
  width: 24px;
  height: 24px;
  background-position: -48px -96px;
}

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

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

Недостатки техники известны поколениям: правка одного значка пересобирает лист целиком, координаты в стилях требуют генерации, и рассинхрон листа со стилями рождает страницы с чужими носами на местах домашних значков. Генерация снимает ручные риски, и в современном конвейере координаты рукописными не бывают вовсе.

Спрайт символов SVG как современный наследник той же идеи

Идея одного файла с набором значков пережила материал: растровый лист сменился файлом символов, где каждый значок описан векторно. Символ объявляется с идентификатором и рамкой, а использование ссылается на символ по имени, и одна запись описывает любое число показов. Масштаб остаётся честным на любой плотности экрана, цвет наследуется из контекста, и значок красится заодно с текстом.

<svg style="display: none">
  <symbol id="icon-home" viewBox="0 0 24 24">
    <path d="M3 11.5 12 4l9 7.5V21h-6v-6h-6v6H3z" />
  </symbol>
</svg>

<svg class="icon"><use href="#icon-home" /></svg>

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

Цвет через наследование от контекста это вежливость тем: значок в кнопке красится акцентом кнопки, в тексте цветом текста, и таблица стилей значков исчезает как класс. Штрих остаётся управляемым: толщина линий масштабируется с размером, и значок в тридцать два пикселя сохраняет вес линий значка в шестнадцать.

Доступность наследуется вместе с остальным: символы несут заголовки для экранных читалок, показываемые элементы берут роль значка с подписью, и векторный подход выигрывает у растровых листов ещё и тем, что значок одинаков на экране и в чтении. Атрибуты описания живут в символе и не требуют дублирования на каждом использовании.

Кеш один файл против многих и арифметика повторных визитов

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

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

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

Числа для внутреннего оправдания собираются из простых наблюдений: число значков на страницу, размер спрайта, доля повторных визитов, и строка экономии в миллисекундах первого показа. Экономика скромная на каждой странице и весомая на миллионе страниц, и именно так выглядят решения о производительности: мелочь, помноженная на масштаб.

Анимация по листу шаги steps и покадровый фокус без видео

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

@keyframes walk {
  to { background-position: -384px 0; }
}

.sprite {
  width: 48px;
  height: 48px;
  animation: walk 0.8s steps(8) infinite;
}

Функция дискретных шагов это сердце жанра: она запрещает интерполяцию между кадрами и показывает ровно столько кадров, сколько задано, и плавность анимации задаётся числом кадров и длительностью цикла. Восемь кадров за восемьсот миллисекунд это живая походка, двадцать четыре кадра это уже мультфильм, и размер листа растёт пропорционально роскоши.

Остановка по последнему кадру и обратные циклы управляются направлением и режимом заполнения анимации: проиграл и замер, проиграл и вернулся, проиграл и скрылся, и выбор режима это выбор смысла сцены. Управление из кода дергает классы состояний, и лист с кадрами остаётся статичным активом, а сценарии его показа живут в стилях.

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

Сборка и поддержка генераторы спрайтов и дисциплина обновления

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

Исходники значков живут обычными файлами в репозитории: папка на проект, вложенные группы по смыслу, имена как идентификаторы, и ревью значка выглядит как ревью картинки с описанием смысла. Генератор запускается сборкой, отпечаток в имени обновляется автоматически, и дисциплина обновления становится дисциплиной одного коммита: исходник, пересборка, страница.

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

Чистка мёртвых значков входит в регламент: генератор умеет отмечать неиспользуемые символы по встречаемости ссылок, и ревизия раз в квартал уносит излишества. Лист, который растёт только вперёд, однажды начинает возить балласт, и векторный спрайт ощущает это быстрее растрового: каждый неиспользуемый путь это строки в каждой странице со встраиванием.

Что ушло в историю шрифты иконок и подсчёт запросов в эпоху мультиплексирования

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

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

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

Порядок оптимизации значков свёрнут в короткую лестницу:

  1. перевести набор на векторные исходники и генератор в конвейере;
  2. собрать символьный спрайт с отпечатком и долгим хранением кеша;
  3. встроить критический минимум символов в страницу, остальное внешним файлом;
  4. покрасить значки наследованием контекста и удалить таблицу стилей иконок;
  5. держать пороги размера листа в сборке и чистить неиспользуемые символы;
  6. покадровые анимации собирать отдельными листами с дискретными шагами;
  7. мерить долю значков в первом показе и знать своё число в миллисекундах.

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