Create CSS/SVG image sprites.

7.8 kB · Created 3mo ago
Markdown

Image Sprites #

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.

Raster Sprites #

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.png

Convert the image to WebP image format:

magick sprites.png -quality 100 -define webp:lossless=true -define webp:method=6 sprites.webp

Suppose 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>

Vector Sprites (SVG) #

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 HTML element is required.
  • The <img> element behaves similarly to inline text:
    • When copied, the alt attribute text is included in the clipboard.
    • It can be selected, which makes it ideal for emoji-like icons in text.
  • 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>
<html>
  <body>
    <img src="/emojis-sprite.svg#circle" alt="circle"/>
    <img src="/emojis-sprite.svg#square" alt="square"/>
  </body>
</html>

CSS Linked Parameters #

This spec introduces a way to pass CSS values into linked resources, such as SVG images, so that they can be used as CSS custom environment variables in the destination resource. This allows easy reuse of "templated" SVG images, which can be adapted to a site’s theme color, etc. easily, without having to modify the source SVG.

https://drafts.csswg.org/css-link-params

https://pepelsbey.dev/articles/svg-link-params

Sign up or Sign in to add to the discussion.