Иконки это самый маленький и самый частый актив интерфейса: десятки картинок по паре килобайт каждая, и наивная страница запрашивает их по одной, платя за каждую накладными расходами, которые превышают сам значок на порядок. Спрайты решают эту арифметику одним приёмом, старым как веб: сложить все значки в один файл и показывать нужный кусок координатами. Приём пережил три эпохи протоколов и не собрался умирать, сменив материал с растровых листов на символьные описания.
Статья разбирает иконочную экономику целиком: цену каждого запроса и почему она не сводится к байтам, классический лист с координатами в стилях, символьный спрайт как современный наследник идеи, кешовую арифметику повторных визитов, покадровую анимацию по листу, генераторы для сборки и честный разбор того, что изменили новые протоколы. С кодом, который работает сегодня, и числами там, где они проверяются за минуту.
Почему иконки стоят дорого при их маленьком размере
Стоимость запроса это не размер файла: это рукопожатия соединения, обмен заголовками, очередь приоритетов и переключение контекста в браузере. Двадцать значков по два килобайта это сорок килобайт полезных байтов и двадцать отдельных походов за каждым, и на слабой сети в дальней клетке походы стоят больше байтов. Старая эпоха считала эту проблему главной, и спрайт был панацеей: один файл, один запрос, все значки.
Вторая составляющая это распараллеливание: браузер ограничен в одновременных запросах к одному хосту, и очередь из мелких значков задерживает крупные ресурсы вроде стилей и шрифтов, стоящие за ней. Спрайт убирает очередь целиком, освобождая канал для тяжёлых активов, и этот эффект на старых протоколах был заметнее экономии самих байтов.
Третья составляющая это кеш: файл значков запрашивается повторно только когда изменился, и стратегия обновления решает, дёшево ли живёт страница. Отдельные значки инвалидируются точечно, но проверка каждого точечного изменения стоит запроса с условием, спрайт инвалидируется целиком, зато проверяется один раз, и баланс решается размером и частотой правок.
Четвёртая составляющая это отрисовка: сотня мелких декодированных картинок держит сотню записей в памяти, один спрайт держит одну. Составляющая скромная, но на страницах с сотнями повторяющихся значков она складывается в заметные строки профиля памяти, и экономика начинается раньше, чем кажется.
Классический лист спрайтов одна картинка и координаты в стилях
Лист это изображение с сеткой значков, и каждый значок показывается окном: элемент берёт лист фоном и сдвигает фон так, чтобы в видимом окне оказался нужный кусок. Техника честна и прозрачна: координаты значков известны при сборке, стили генерируются автоматически, и страница не несёт ни одного дополнительного запроса на иконку.
.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;
}
Функция дискретных шагов это сердце жанра: она запрещает интерполяцию между кадрами и показывает ровно столько кадров, сколько задано, и плавность анимации задаётся числом кадров и длительностью цикла. Восемь кадров за восемьсот миллисекунд это живая походка, двадцать четыре кадра это уже мультфильм, и размер листа растёт пропорционально роскоши.
Остановка по последнему кадру и обратные циклы управляются направлением и режимом заполнения анимации: проиграл и замер, проиграл и вернулся, проиграл и скрылся, и выбор режима это выбор смысла сцены. Управление из кода дергает классы состояний, и лист с кадрами остаётся статичным активом, а сценарии его показа живут в стилях.
Производительность жанра скромна и предсказуема: сдвиг фона это дешёвая операция композитора, лист в памяти один, и покадровый герой стоит дешевле любого видео с тем же содержанием. Предел жанра размер листа: длинные сценарии режутся на листы по сценам, и память уважается как на спрайтах иконок.
Сборка и поддержка генераторы спрайтов и дисциплина обновления
Спрайт в современном продукте не собирается руками: генератор берёт папку исходников, собирает лист или файл символов, пишет координаты и стили, и правка значка это замена исходника с пересборкой. Конвейер закрывает рассинхрон материала и координат по построению, и ручная правка сгенерированного файла остаётся главным запретом конвейера.
Исходники значков живут обычными файлами в репозитории: папка на проект, вложенные группы по смыслу, имена как идентификаторы, и ревью значка выглядит как ревью картинки с описанием смысла. Генератор запускается сборкой, отпечаток в имени обновляется автоматически, и дисциплина обновления становится дисциплиной одного коммита: исходник, пересборка, страница.
Контроль размера встроен в сборку: генератор предупреждает о значке, раздувшем лист, о листе, перешагнувшем комфортный размер, и об исходниках с лишним весом. Пороги настраиваются под экономику проекта, и сигнал о раздувании приходит в запросе на слияние, а не в отчёте о скорости через месяц после релиза.
Чистка мёртвых значков входит в регламент: генератор умеет отмечать неиспользуемые символы по встречаемости ссылок, и ревизия раз в квартал уносит излишества. Лист, который растёт только вперёд, однажды начинает возить балласт, и векторный спрайт ощущает это быстрее растрового: каждый неиспользуемый путь это строки в каждой странице со встраиванием.
Что ушло в историю шрифты иконок и подсчёт запросов в эпоху мультиплексирования
Шрифт значков был конкурентом спрайтов прошлой эпохи: значки как глифы в шрифте, один файл, управление цветом как текстом. Приём проиграл по существу: начертания зависели от платформенных движков рендеринга глифов, позиционирование по базовой линии скакало, добавление значка требовало пересборки шрифта со сменой кодов, и доступность страдала от чтения глифов как букв. Спрайт символов унаследовал достоинства без этих болезней и закрыл жанр.
Подсчёт запросов изменился с протоколами: мультиплексирование устранило очередь к хосту, и двадцать мелких запросов больше не душат крупные. Арифметика не отменилась, а сместилась: накладные расходы каждого запроса остались, приоритеты остались, кеш остался, и спрайт продолжает выигрывать не числом запросов, а числом проверок, размером памяти и чистотой кеша.
Осталась истина, которая переживает все протоколы: суммарные байты важнее числа файлов, а суммарные байты зависят от формата значков. Вектор сжимает повторяющиеся формы лучше растра, лист значков одного стиля весит меньше суммы его растровых копий, и выбор формата это первая строка оптимизации, число запросов вторая.
Порядок оптимизации значков свёрнут в короткую лестницу:
- перевести набор на векторные исходники и генератор в конвейере;
- собрать символьный спрайт с отпечатком и долгим хранением кеша;
- встроить критический минимум символов в страницу, остальное внешним файлом;
- покрасить значки наследованием контекста и удалить таблицу стилей иконок;
- держать пороги размера листа в сборке и чистить неиспользуемые символы;
- покадровые анимации собирать отдельными листами с дискретными шагами;
- мерить долю значков в первом показе и знать своё число в миллисекундах.
Значки это мелочь, из которой складывается скорость: страница без очередей за мелочью грузится как единый организм, и пользователь не знает, почему ему быстро, он просто не ждёт. Спрайт в его современном символьном виде остаётся самой честной формой этой дисциплины: одна сборка, один кеш, одна строка в бюджете производительности.