Skip to content

Instantly share code, notes, and snippets.

@flipeador
Last active August 27, 2025 16:11
Show Gist options
  • Select an option

  • Save flipeador/4963f0bddfc9e4456cdf982a905a86d2 to your computer and use it in GitHub Desktop.

Select an option

Save flipeador/4963f0bddfc9e4456cdf982a905a86d2 to your computer and use it in GitHub Desktop.
Create CSS/SVG image sprites.

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 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>
<!DOCTYPE html>
  <body>
    <img src="/emojis-sprite.svg#circle" alt="circle"/>
    <img src="/emojis-sprite.svg#square" alt="square"/>
  </body>
</html>
@flipeador
Copy link
Copy Markdown
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment