Create CSS/SVG image sprites.
0
image-sprites.md
1# Image Sprites
2
3An image sprite is a collection of images put into a single image or file.
4
5Helps reduce the number of HTTP requests, memory and bandwidth usage.
6
7They are often used to significantly improve performance in emoji selectors.
8
9## Raster Sprites
10
11<details>
12<summary><h4>How to create a WebP emojis sprite.</h4></summary>
13
14Download [ImageMagick][imd].
15
16Create an image sprite using the [Montage][imm] tool:
17
18```bash
19montage png/*.png -background none -geometry +0+0 PNG32:sprites.png
20```
21
22Convert the image to [WebP][imw] image format:
23
24```bash
25magick sprites.png -quality 100 -define webp:lossless=true -define webp:method=6 sprites.webp
26```
27
28Suppose we have an image sprite with `72x72` emojis from [Unicode 16.0][u16].
29
30Use the following JavaScript code to calculate the background size and position:
31
32```js
33const size = 72; // size of each emoji
34const width = 4536; // image sprite width
35const height = 4464; // image sprite height
36
37const horizontal = width / size; // max number of emojis per row (63)
38const vertical = height / size; // max number of emojis per column (62)
39
40// Calculate the background size.
41// 100% is the size of the element that has the background image.
42// We need to multiply by the number of emojis per row and column.
43console.log('Background width:', 100 * horizontal); // 6300%
44console.log('Background height:', 100 * vertical); // 6200%
45// CSS -> background-size: 6300% 6200%
46
47// Calculate the emoji position at column 48, row 56.
48// Column 1 and row 1 are always 0%, the max is 100%.
49const position = calcPosition(48, 56);
50console.log('Background position X:', position.x); // 75.806%
51console.log('Background position Y:', position.y); // 90.164%
52// CSS -> background-position: 75.806% 90.164%;
53
54function calcPosition(col, row, d=3) {
55 const x = 100 / (horizontal - 1) * --col;
56 const y = 100 / (vertical - 1) * --row;
57 return { x: x.toFixed(d), y: y.toFixed(d) };
58}
59```
60
61HTML example of how to use the WebP image sprite:
62
63```html
64<!DOCTYPE html>
65 <head>
66 <style>
67 html { color-scheme: dark; }
68 .emoji {
69 /*
70 The <img> element can be any size.
71 Values greater than the emoji size will make the image look blurry.
72 Set 'image-rendering' to 'pixelated' to make the image less blurry.
73 */
74 width: 72px;
75 aspect-ratio: 1;
76 image-rendering: pixelated;
77 background-size: 6300% 6200%;
78 background-image: url('sprites.webp');
79 }
80 .emoji28x1 { background-position: 43.548% 0.000%; }
81 .emoji15x45 { background-position: 22.581% 72.131%; }
82 .emoji48x56 { background-position: 75.806% 90.164%; }
83 </style>
84 </head>
85 <body>
86 <!-- Argentina Flag -->
87 <p class="emoji emoji28x1"></p>
88 <!-- Parrot -->
89 <p class="emoji emoji15x45"></p>
90 <!-- Fingerprint (Unicode 16.0) -->
91 <p class="emoji emoji48x56"></p>
92 </body>
93</html>
94```
95
96</details>
97
98## Vector Sprites (SVG)
99
100<details>
101<summary><h4>How to use the <use> element to reference SVG images.</h4></summary>
102
103Use the [`<use>`][use] element to reference only a part of an SVG image defined elsewhere.
104
105```html
106<!DOCTYPE html>
107 <body>
108 <svg style="display: none;">
109 <!-- Define the SVG images using the <symbol> element. -->
110 <!-- Then you can reference them with the <use> element. -->
111 <symbol id="circle" viewBox="0 0 24 24" stroke="currentColor" fill="none">
112 <circle cx="12" cy="12" r="10"/>
113 </symbol>
114 <symbol id="square" viewBox="0 0 24 24" stroke="currentColor" fill="none">
115 <rect width="18" height="18" x="3" y="3" rx="2"/>
116 </symbol>
117 </svg>
118 <!-- Circle SVG -->
119 <p>
120 <svg style="width: 72px; aspect-ratio: 1;">
121 <use href="#circle"/>
122 </svg>
123 </p>
124 <!-- Square SVG -->
125 <p>
126 <svg style="width: 72px; aspect-ratio: 1;">
127 <use href="#square"/>
128 </svg>
129 </p>
130 </body>
131</html>
132```
133
134The [svg-sprite][ssp] library can be used to create SVG sprites of several types.
135
136The following example combines `icons/*/*.svg` into a single file for each subdirectory.
137
138```js
139import fs from 'node:fs';
140import path from 'node:path';
141import SVGSpriter from 'svg-sprite';
142
143if (!fs.existsSync('sprites'))
144 fs.mkdirSync('sprites');
145
146const iter = fs.globSync('icons/*/*.svg');
147
148const groups = Object.groupBy(
149 Array.from(iter),
150 icon => icon.split(path.sep).at(-2)
151);
152
153for (const [sprite, icons] of Object.entries(groups)) {
154 const spriter = new SVGSpriter({
155 mode: {
156 symbol: {
157 inline: true
158 }
159 }
160 });
161
162 for (const icon of icons) {
163 const name = `icon:${sprite}.${path.basename(icon)}`;
164 spriter.add(name, null, fs.readFileSync(icon, 'utf8'));
165 }
166
167 const output = `sprites/${sprite}.svg`;
168 const { result } = await spriter.compileAsync();
169
170 for (const mode of Object.values(result))
171 fs.writeFileSync(output, mode.sprite.contents, 'utf8');
172}
173
174/*
175 INPUT
176 --------------------------------------------------
177 ./icons/
178 misc/
179 circle.svg
180 square.svg
181 emojis/
182 butterfly.svg
183
184 OUTPUT
185 --------------------------------------------------
186 ./sprites/
187 misc.svg
188 emojis.svg
189
190 USAGE
191 --------------------------------------------------
192 <svg><use href="#icon:misc.circle"/></svg>
193 <svg><use href="#icon:misc.square"/></svg>
194 <svg><use href="#icon:emojis.butterfly"/></svg>
195*/
196```
197
198</details>
199
200<details>
201<summary><h4>How to use the <img> element to display SVG images.</h4></summary>
202
203Use the [`<img>`][img] element with a [fragment identifier][fra] to display only a part of an SVG image.
204
205Using [SVG Fragment Identifiers][sfi] with [`<img>`][img] has a couple of advantages over other methods:
206
207- Only a single HTML element is required.
208- The [`<img>`][img] element behaves similarly to inline text:
209 - When copied, the `alt` attribute text is included in the clipboard.
210 - It can be selected, which makes it ideal for emoji-like icons in text.
211- Natively supports fallback behavior if the image or fragment fails to load.
212
213However, it's worth noting that `<svg>` with [`<use>`][use] remains valuable for:
214
215- Reusable SVG symbols in contexts where you need finer control (e.g., altering fill, stroke).
216- Interactive or animated graphics where direct access to the DOM structure of the SVG is needed.
217
218```html
219<!-- emojis-sprite.svg -->
220
221<svg xmlns="http://www.w3.org/2000/svg">
222 <defs>
223 <style>
224 g:not(:target) {
225 display: none;
226 }
227 </style>
228 </defs>
229 <!-- Circle SVG -->
230 <svg viewBox="0 0 24 24" stroke="red" fill="none">
231 <g id="circle">
232 <circle cx="12" cy="12" r="10"/>
233 </g>
234 </svg>
235 <!-- Square SVG -->
236 <svg viewBox="0 0 24 24" stroke="red" fill="none">
237 <g id="square">
238 <rect width="18" height="18" x="3" y="3" rx="2"/>
239 </g>
240 </svg>
241</svg>
242```
243
244```html
245<html>
246 <body>
247 <img src="/emojis-sprite.svg#circle" alt="circle"/>
248 <img src="/emojis-sprite.svg#square" alt="square"/>
249 </body>
250</html>
251```
252
253</details>
254
255<!-- REFERENCE LINKS -->
256[imd]: https://imagemagick.org/script/download.php
257[imm]: https://imagemagick.org/script/montage.php
258[imw]: https://imagemagick.org/script/webp.php
259
260[bgi]: https://developer.mozilla.org/docs/Web/CSS/background-image
261[bgs]: https://developer.mozilla.org/docs/Web/CSS/background-size
262[bgp]: https://developer.mozilla.org/docs/Web/CSS/background-position
263[use]: https://developer.mozilla.org/docs/Web/SVG/Element/use
264[img]: https://developer.mozilla.org/docs/Web/SVG/Element/img
265[fra]: https://developer.mozilla.org/en-US/docs/Web/URI/Fragment
266
267[u16]: https://www.unicode.org/versions/Unicode16.0.0
268[sfi]: https://www.w3.org/TR/SVG/linking.html#SVGFragmentIdentifiers
269
270[ssp]: https://github.com/svg-sprite/svg-sprite