An image sprite is a collection of images put into a single image or file.
Helps reduce the number of HTTP requests, memory and bandwidth usage.
They are often used to significantly improve performance in emoji selectors.
How to create a WebP emojis sprite.
Download ImageMagick.
Create an image sprite using the Montage tool:
montage png/*.png -background none -geometry +0+0 PNG32:sprites.pngConvert the image to WebP image format:
magick sprites.png -quality 100 -define webp:lossless=true -define webp:method=6 sprites.webpSuppose we have an image sprite with 72x72 emojis from Unicode 16.0.
Use the following JavaScript code to calculate the background size and position:
const size = 72; // size of each emoji
const width = 4536; // image sprite width
const height = 4464; // image sprite height
const horizontal = width / size; // max number of emojis per row (63)
const vertical = height / size; // max number of emojis per column (62)
// Calculate the background size.
// 100% is the size of the element that has the background image.
// We need to multiply by the number of emojis per row and column.
console.log('Background width:', 100 * horizontal); // 6300%
console.log('Background height:', 100 * vertical); // 6200%
// CSS -> background-size: 6300% 6200%
// Calculate the emoji position at column 48, row 56.
// Column 1 and row 1 are always 0%, the max is 100%.
const position = calcPosition(48, 56);
console.log('Background position X:', position.x); // 75.806%
console.log('Background position Y:', position.y); // 90.164%
// CSS -> background-position: 75.806% 90.164%;
function calcPosition(col, row, d=3) {
const x = 100 / (horizontal - 1) * --col;
const y = 100 / (vertical - 1) * --row;
return { x: x.toFixed(d), y: y.toFixed(d) };
}HTML example of how to use the WebP image sprite:
<!DOCTYPE html>
<head>
<style>
html { color-scheme: dark; }
.emoji {
/*
The <img> element can be any size.
Values greater than the emoji size will make the image look blurry.
Set 'image-rendering' to 'pixelated' to make the image less blurry.
*/
width: 72px;
aspect-ratio: 1;
image-rendering: pixelated;
background-size: 6300% 6200%;
background-image: url('sprites.webp');
}
.emoji28x1 { background-position: 43.548% 0.000%; }
.emoji15x45 { background-position: 22.581% 72.131%; }
.emoji48x56 { background-position: 75.806% 90.164%; }
</style>
</head>
<body>
<!-- Argentina Flag -->
<p class="emoji emoji28x1"></p>
<!-- Parrot -->
<p class="emoji emoji15x45"></p>
<!-- Fingerprint (Unicode 16.0) -->
<p class="emoji emoji48x56"></p>
</body>
</html>How to use the <use> element to reference SVG images.
Use the <use> element to reference only a part of an SVG image defined elsewhere.
<!DOCTYPE html>
<body>
<svg style="display: none;">
<!-- Define the SVG images using the <symbol> element. -->
<!-- Then you can reference them with the <use> element. -->
<symbol id="circle" viewBox="0 0 24 24" stroke="currentColor" fill="none">
<circle cx="12" cy="12" r="10"/>
</symbol>
<symbol id="square" viewBox="0 0 24 24" stroke="currentColor" fill="none">
<rect width="18" height="18" x="3" y="3" rx="2"/>
</symbol>
</svg>
<!-- Circle SVG -->
<p>
<svg style="width: 72px; aspect-ratio: 1;">
<use href="#circle"/>
</svg>
</p>
<!-- Square SVG -->
<p>
<svg style="width: 72px; aspect-ratio: 1;">
<use href="#square"/>
</svg>
</p>
</body>
</html>The svg-sprite library can be used to create SVG sprites of several types.
The following example combines icons/*/*.svg into a single file for each subdirectory.
import fs from 'node:fs';
import path from 'node:path';
import SVGSpriter from 'svg-sprite';
if (!fs.existsSync('sprites'))
fs.mkdirSync('sprites');
const iter = fs.globSync('icons/*/*.svg');
const groups = Object.groupBy(
Array.from(iter),
icon => icon.split(path.sep).at(-2)
);
for (const [sprite, icons] of Object.entries(groups)) {
const spriter = new SVGSpriter({
mode: {
symbol: {
inline: true
}
}
});
for (const icon of icons) {
const name = `icon:${sprite}.${path.basename(icon)}`;
spriter.add(name, null, fs.readFileSync(icon, 'utf8'));
}
const output = `sprites/${sprite}.svg`;
const { result } = await spriter.compileAsync();
for (const mode of Object.values(result))
fs.writeFileSync(output, mode.sprite.contents, 'utf8');
}
/*
INPUT
--------------------------------------------------
./icons/
misc/
circle.svg
square.svg
emojis/
butterfly.svg
OUTPUT
--------------------------------------------------
./sprites/
misc.svg
emojis.svg
USAGE
--------------------------------------------------
<svg><use href="#icon:misc.circle"/></svg>
<svg><use href="#icon:misc.square"/></svg>
<svg><use href="#icon:emojis.butterfly"/></svg>
*/How to use the <img> element to display SVG images.
Use the <img> element with a fragment identifier to display only a part of an SVG image.
Using SVG Fragment Identifiers with <img> has a couple of advantages over other methods:
- Only a single element is required.
- The
<img>element behaves similarly to inline text:- When copied, the
altattribute text is included in the clipboard. - It can be selected, which makes it ideal for emoji-like icons in text.
- When copied, the
- Natively supports fallback behavior if the image or fragment fails to load.
However, it's worth noting that <svg> with <use> remains valuable for:
- Reusable SVG symbols in contexts where you need finer control (e.g., altering fill, stroke).
- Interactive or animated graphics where direct access to the DOM structure of the SVG is needed.
<!-- emojis-sprite.svg -->
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<style>
g:not(:target) {
display: none;
}
</style>
</defs>
<!-- Circle SVG -->
<svg viewBox="0 0 24 24" stroke="red" fill="none">
<g id="circle">
<circle cx="12" cy="12" r="10"/>
</g>
</svg>
<!-- Square SVG -->
<svg viewBox="0 0 24 24" stroke="red" fill="none">
<g id="square">
<rect width="18" height="18" x="3" y="3" rx="2"/>
</g>
</svg>
</svg><!DOCTYPE html>
<body>
<img src="/emojis-sprite.svg#circle" alt="circle"/>
<img src="/emojis-sprite.svg#square" alt="square"/>
</body>
</html>
https://drafts.csswg.org/css-link-params