Skip to content
Make with
m

maplibre-transition

Animate paint properties per feature

Maintained by Jamie Popkin

A MapLibre GL JS plugin that animates paint properties (radius, color, opacity, width) of individual features through feature state, with easing, delays and multi-step values.

Live demo

maplibre-transition running on MapLibre GL JS in your browser

Loading the map…

Demo code
import MaplibreTransition from "maplibre-transition";
import type { PluginDemo } from "../types";

export default {
  center: [12, 50], // Europe
  zoom: 3.8,
  async run({ map }) {
    MaplibreTransition.init(map);

    const cities: [string, number, number][] = [
      ["Lisbon", -9.14, 38.72],
      ["Madrid", -3.7, 40.42],
      ["Paris", 2.35, 48.86],
      ["London", -0.13, 51.51],
      ["Amsterdam", 4.9, 52.37],
      ["Berlin", 13.4, 52.52],
      ["Copenhagen", 12.57, 55.68],
      ["Stockholm", 18.07, 59.33],
      ["Warsaw", 21.01, 52.23],
      ["Vienna", 16.37, 48.21],
      ["Rome", 12.5, 41.9],
      ["Athens", 23.73, 37.98],
    ];
    map.addSource("cities", {
      type: "geojson",
      data: {
        type: "FeatureCollection",
        features: cities.map(([name, lng, lat], id) => ({
          type: "Feature",
          id,
          properties: { name },
          geometry: { type: "Point", coordinates: [lng, lat] },
        })),
      },
    });
    map.addLayer({
      id: "cities",
      type: "circle",
      source: "cities",
      paint: {
        "circle-radius": 2,
        "circle-color": "#29335c",
        "circle-stroke-color": "#fff",
        "circle-stroke-width": 1,
      },
    });
    await map.once("idle");

    // Animate each city's paint properties on its own, one after another.
    map.queryRenderedFeatures({ layers: ["cities"] }).forEach((feature) => {
      map.transition(feature, {
        duration: 900,
        delay: Number(feature.id) * 150,
        ease: "cubic",
        paint: { "circle-radius": [2, 16, 9], "circle-color": ["#29335c", "#e4572e"] },
      });
    });

    // And grow a city while the pointer is on it.
    map.on(
      "mouseenter",
      "cities",
      (e) =>
        e.features?.[0] &&
        map.transition(e.features[0], { duration: 300, paint: { "circle-radius": [null, 16] } }),
    );
    map.on("mouseleave", "cities", () =>
      map
        .queryRenderedFeatures({ layers: ["cities"] })
        .forEach((f) =>
          map.transition(f, { duration: 300, paint: { "circle-radius": [null, 9] } }),
        ),
    );
  },
} satisfies PluginDemo;