Skip to content

Repository files navigation

maplibre-image-plugins

Live demo: both plugins on a real map, with controls for the pulsing dot.

Animated style image plugins for MapLibre GL JS, built on the public StyleImageInterface API. Any animated format the browser's ImageDecoder understands works: GIF, animated WebP, APNG, animated AVIF.

AnimatedStyleImage animates on the GPU: every frame is uploaded once, and advancing a frame is a single GPU-to-GPU copy into MapLibre's atlas. The map can go idle between frames. This rides on maplibre-gl-js#7954, which lets a style image render straight into the icon atlas via StyleImageWebGLData instead of rewriting pixels on the CPU: with 50 animated 256 px icons, that PR measured 18.0 ms per frame the old way against 0.64 ms on the GPU, about 28x, and more on phones where CPU time is scarcer.

PulsingDotStyleImage is a pulsing location dot drawn entirely by a fragment shader, with no image asset or per-frame pixel upload. The dot, stroke, and halo each take a radius and a color, and the pulse speed is configurable.

Safari has no ImageDecoder, so only GIF works there, decoded by modern-gif loaded on demand. WebKit landed the WebCodecs image API in August 2026 (bug 315546), enabled by default, so the fallback can go once that ships.

Getting started

npm install maplibre-image-plugins

maplibre-gl >= 6.5, the first release with StyleImageWebGLData support, is a peer dependency.

import {AnimatedStyleImage, PulsingDotStyleImage} from 'maplibre-image-plugins';

map.addImage('location', new PulsingDotStyleImage({dotColor: 'tomato', haloRadius: 75}), {pixelRatio: 2});
map.addImage('spinner', await AnimatedStyleImage.fromURL('/spinner.gif'), {pixelRatio: 2});

Without a bundler, everything loads from unpkg. The import map resolves the package's maplibre-gl import plus the GIF decoder it lazy-loads on browsers without ImageDecoder:

<script type="importmap">
    {
        "imports": {
            "maplibre-gl": "https://unpkg.com/maplibre-gl@^6.5.0/dist/maplibre-gl.mjs",
            "modern-gif": "https://unpkg.com/modern-gif@^2.1.0/dist/index.mjs",
            "modern-palette": "https://unpkg.com/modern-palette@^2.0.0/dist/index.mjs"
        }
    }
</script>
<script type="module">
    import {PulsingDotStyleImage} from 'https://unpkg.com/maplibre-image-plugins@^0.1.0/dist/index.js';
</script>

Both plugins wake the map on a timer and let it rest between animation frames. On releases without maplibre-gl-js#8208 (merged, not yet in a release as of maplibre-gl 6.6), each frame drags a ~300 ms tail of extra renders and symbol re-placements out of the placement machinery unless the map is created with fadeDuration: 0: one 30 fps animated icon on an otherwise static map meant 88 renders/s and 2.7 idles/s. On releases that include it, the same map renders 30 frames a second and idles 30 times a second with no configuration.

Developing

npm install
npm test
npm run fix

About

Animated style image plugins for MapLibre GL JS, drawn on the GPU

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages