npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

maplibre-animated-shaders

v0.2.0

Published

Animated GLSL shaders for MapLibre GL JS

Downloads

186

Readme

MapLibre Animated Shaders

Animated GLSL shaders for MapLibre GL JS. Add stunning visual effects to your map layers with smooth WebGL-powered animations.

npm version License: MIT

Features

  • WebGL-powered animations - Smooth 60fps animations using custom GLSL shaders
  • Multiple geometry types - Support for points, lines, polygons, and global (full-screen) effects
  • Data-driven styling - Use MapLibre expressions for per-feature dynamic properties
  • Per-feature animation control - Play, pause, and control animations on individual features
  • Plugin architecture - Extensible system for organizing and sharing shaders
  • TypeScript first - Full type definitions with strict typing
  • Built-in shader library - Ready-to-use GLSL utilities (noise, easing, colors, shapes)
  • Typed event system - Subscribe to shader lifecycle events
  • Error hierarchy - Typed errors with error codes for robust error handling
  • Performance optimized - Shader program caching, object pooling, buffer reuse

Implementation Progress

| Phase | Feature | Status | |-------|---------|--------| | 1.1 | WebGL 2.0 Support avec Fallback | ✅ Complete | | 1.2 | Config Immutability (deep-freeze) | ✅ Complete | | 1.3 | Complete JSDoc documentation | ✅ Complete | | 2.1 | Instanced Rendering | ✅ Complete | | 2.2 | Frustum Culling | ✅ Complete | | 2.3 | Level of Detail (LOD) | ✅ Complete | | 2.4 | Adaptive Frame Rate | ✅ Complete | | 3.1 | Textures and Sprites | ✅ Complete | | 3.2 | Post-Processing Pipeline | ✅ Complete | | 3.3 | Shader Transitions | ✅ Complete | | 4.1 | Terrain/3D Shaders | ✅ Complete | | 5.1 | Worker Thread Support | ✅ Complete |

Installation

npm install maplibre-animated-shaders maplibre-gl

Quick Start

import maplibregl from 'maplibre-gl';
import { createShaderManager, examplePlugin } from 'maplibre-animated-shaders';

// Create your map
const map = new maplibregl.Map({
  container: 'map',
  style: 'https://demotiles.maplibre.org/style.json',
  center: [2.34, 48.858],
  zoom: 12
});

map.on('load', () => {
  // Add a circle layer to your map
  map.addSource('points', {
    type: 'geojson',
    data: {
      type: 'FeatureCollection',
      features: [
        { type: 'Feature', geometry: { type: 'Point', coordinates: [2.34, 48.858] }, properties: {} }
      ]
    }
  });

  map.addLayer({
    id: 'my-points',
    type: 'circle',
    source: 'points',
    paint: { 'circle-radius': 20, 'circle-color': '#3b82f6' }
  });

  // Create the shader manager and register the example plugin
  const shaderManager = createShaderManager(map);
  shaderManager.use(examplePlugin);

  // Apply an animated shader to the layer
  shaderManager.register('my-points', 'example:point', {
    color: '#3b82f6',
    speed: 1.0,
    rings: 3,
    maxRadius: 40
  });

  // Start the animation
  shaderManager.play();
});

API Reference

ShaderManager

The main orchestrator for managing animated shaders on your map.

const shaderManager = createShaderManager(map, options?);

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | debug | boolean | false | Enable debug logging | | maxFPS | number | 60 | Maximum frames per second | | collectMetrics | boolean | false | Enable performance metrics collection |

Methods

| Method | Description | |--------|-------------| | use(plugin) | Register a shader plugin | | useAsync(pluginName) | Lazy-load and register a built-in plugin | | register(layerId, shaderName, config?, interactivityConfig?) | Apply a shader to a layer | | unregister(layerId) | Remove a shader from a layer | | play(layerId?) | Start animation (all or specific layer) | | pause(layerId?) | Pause animation (all or specific layer) | | setSpeed(layerId, speed) | Set animation speed for a layer | | updateConfig(layerId, config) | Update shader configuration | | getMetrics() | Get performance metrics | | destroy() | Clean up all resources |

Shader Configuration

Each shader has its own configuration options. Here's an example for the point shader:

shaderManager.register('my-layer', 'example:point', {
  // Static values
  color: '#ff0000',
  speed: 1.5,
  rings: 3,

  // Or data-driven with MapLibre expressions
  color: ['get', 'status_color'],
  intensity: ['match', ['get', 'priority'],
    'high', 1.0,
    'low', 0.3,
    0.5
  ]
});

Per-Feature Animation Control

Enable individual control over each feature's animation:

shaderManager.register('my-layer', 'example:point', config, {
  perFeatureControl: true,
  initialState: { playing: false },
  featureIdProperty: 'id',
  onClick: (feature, state, controller) => {
    controller.toggleFeature(feature.id);
  },
  onHover: (feature, state) => {
    console.log(`Hovering feature ${feature.id}`);
  }
});

Built-in Shaders

The examplePlugin includes shaders for all geometry types:

Point Shader: "Pulse Marker"

Animated concentric rings radiating from points.

{
  color: string,       // Color (hex or rgba)
  speed: number,       // Animation speed multiplier
  rings: number,       // Number of visible rings
  maxRadius: number,   // Maximum radius in pixels
  thickness: number,   // Ring thickness
  fadeOut: boolean,    // Fade rings as they expand
  easing: 'linear' | 'easeOut' | 'easeInOut' | 'elastic'
}

Line Shader: "Flow Line"

Animated dashes flowing along lines.

{
  color: string,
  speed: number,
  dashLength: number,    // Dash length ratio (0-1)
  gapLength: number,     // Gap length ratio (0-1)
  direction: 'forward' | 'backward',
  gradient: boolean,     // Gradient within each dash
  glow: boolean,         // Glow effect
  width: number          // Line width in pixels
}

Polygon Shader: "Wave Polygon"

Wave and noise patterns on filled areas.

{
  color: string,
  speed: number,
  waves: number,        // Number of wave cycles
  scale: number,        // Pattern scale
  amplitude: number,    // Wave amplitude
  useNoise: boolean,    // Use noise pattern
  pattern: 'waves' | 'ripple' | 'noise'
}

Global Shader: "Grid Overlay"

Full-screen sci-fi grid effects.

{
  color: string,
  speed: number,
  gridSize: number,      // Grid cell size
  lineWidth: number,     // Grid line width
  pulseWave: boolean,    // Radial pulse effect
  scanLine: boolean,     // Horizontal scan line
  glowIntensity: number  // Glow intensity
}

Presets

Shaders can include presets for common configurations:

// List available presets
const presets = shaderManager.getAllPresets();
// ['example:point-alert', 'example:point-notification', ...]

// Use a preset
shaderManager.registerPreset('my-layer', 'example:point-alert');

Event System

Subscribe to shader lifecycle events:

import { globalEventEmitter } from 'maplibre-animated-shaders';

// Listen to shader registration
const unsubscribe = globalEventEmitter.on('shader:registered', (event) => {
  console.log(`Shader registered on layer: ${event.layerId}`);
});

// Listen to errors
globalEventEmitter.on('error', (event) => {
  console.error(`Error: ${event.error.message}`, event.context);
});

// Available events:
// - 'shader:registered', 'shader:unregistered', 'shader:configUpdated'
// - 'shader:play', 'shader:pause', 'shader:speedChanged'
// - 'plugin:registered', 'plugin:unregistered'
// - 'error', 'performance:warning', 'performance:frame'
// - 'destroyed'

// Unsubscribe when done
unsubscribe();

Error Handling

The library provides typed errors for better error handling:

import {
  ShaderManagerError,
  ShaderNotFoundError,
  LayerNotFoundError,
  isShaderManagerError,
  hasErrorCode,
  ErrorCodes
} from 'maplibre-animated-shaders';

try {
  shaderManager.register('unknown-layer', 'example:point', {});
} catch (error) {
  if (isShaderManagerError(error)) {
    console.log(`Error code: ${error.code}`);
    console.log(`Details: ${JSON.stringify(error.details)}`);
  }

  if (hasErrorCode(error, ErrorCodes.LAYER_NOT_FOUND)) {
    console.log('The layer does not exist on the map');
  }
}

Worker Thread Support

For heavy geometry processing on large datasets (10k+ features), use the GeometryWorker to offload CPU-intensive operations to a separate thread, keeping the main thread responsive.

Basic Usage

import { GeometryWorker } from 'maplibre-animated-shaders';

// Create worker
const worker = new GeometryWorker();

// Initialize (creates inline worker)
await worker.initialize();

// Process features off the main thread
const features = await loadLargeGeoJSON(); // 50k+ features

const result = await worker.processGeometry(features, {
  computeBounds: true,
  generateBuffers: true,
  simplification: 0.5
});

// Use the results
console.log(`Processed ${result.featureCount} features`);
console.log(`Generated ${result.vertexCount} vertices`);

// Clean up when done
worker.dispose();

Available Operations

| Method | Description | |--------|-------------| | processGeometry(features, options) | Process features with multiple operations | | simplify(features, tolerance) | Simplify geometries using Douglas-Peucker | | computeBounds(features) | Calculate bounding boxes for all features | | generateBuffers(features, stride) | Generate vertex and index buffers |

Processing Options

interface GeometryProcessOptions {
  simplification?: number;   // Douglas-Peucker tolerance (0-1)
  computeBounds?: boolean;   // Calculate feature bounds
  generateBuffers?: boolean; // Generate WebGL buffers
  stride?: number;           // Vertex buffer stride
}

Fallback Behavior

If Web Workers are not available (e.g., in some environments), the GeometryWorker automatically falls back to main-thread processing:

// Check support before intensive operations
if (GeometryWorker.isSupported()) {
  // Use worker for large datasets
  const worker = new GeometryWorker();
  await worker.initialize();
  // ...
} else {
  // Handle synchronously or use smaller batches
  console.warn('Workers not supported, using main thread');
}

Performance Tips

  • Large datasets: Use workers for 10k+ features
  • Batch processing: Group features when possible
  • Simplification: Apply simplification at lower zoom levels
  • Timeout handling: Configure timeout for large operations
const worker = new GeometryWorker({ timeout: 60000 }); // 60 second timeout

// Handle errors gracefully
worker.onError = (error) => {
  console.error('Worker error:', error);
  // Fallback to main thread processing
};

GLSL Utilities

The library exports reusable GLSL functions for your custom shaders:

import { glsl } from 'maplibre-animated-shaders';

// Available modules:
glsl.noise    // Perlin, Simplex, Value noise, FBM
glsl.easing   // 15+ easing functions
glsl.colors   // Color space conversions, blend modes
glsl.shapes   // SDF primitives and operations

Noise Functions

float snoise(vec2 v)           // Simplex noise 2D
float snoise(vec3 v)           // Simplex noise 3D
float fbm(vec2 p, int octaves) // Fractal Brownian Motion
float random(vec2 st)          // Hash-based random

Easing Functions

float easeOutQuad(float t)
float easeInOutCubic(float t)
float easeOutElastic(float t)
float easeOutBounce(float t)
// ... and many more

Color Functions

vec3 rgb2hsl(vec3 c)
vec3 hsl2rgb(vec3 c)
vec3 adjustSaturation(vec3 c, float amount)
vec3 blendOverlay(vec3 base, vec3 blend)

Shape Functions (SDF)

float sdCircle(vec2 p, float r)
float sdBox(vec2 p, vec2 b)
float sdRing(vec2 p, float r, float w)
float opSmoothUnion(float d1, float d2, float k)

Creating Custom Plugins

See PLUGIN_GUIDE.md for detailed instructions on creating your own shader plugins.

Quick example:

import { ShaderPlugin, defineShader } from 'maplibre-animated-shaders';

const myShader = defineShader({
  name: 'glow',
  displayName: 'Glowing Points',
  geometry: 'point',
  fragmentShader: `
    precision mediump float;
    uniform float u_time;
    uniform vec4 u_color;
    varying vec2 v_pos;

    void main() {
      float dist = length(v_pos);
      float glow = 0.5 + 0.5 * sin(u_time * 3.0);
      float alpha = (1.0 - dist) * glow;
      gl_FragColor = vec4(u_color.rgb, alpha * u_color.a);
    }
  `,
  defaultConfig: { color: '#ffffff', speed: 1.0 },
  configSchema: {
    color: { type: 'color', label: 'Color' },
    speed: { type: 'number', label: 'Speed', min: 0.1, max: 5 }
  },
  getUniforms: (config, time) => ({
    u_time: time * config.speed,
    u_color: hexToRgba(config.color)
  })
});

export const myPlugin: ShaderPlugin = {
  name: 'my-shaders',
  version: '1.0.0',
  shaders: [myShader]
};

Architecture

See ARCHITECTURE.md for detailed technical documentation.

Browser Support

  • Chrome/Edge 80+
  • Firefox 75+
  • Safari 14+
  • Any browser with WebGL 1.0 support

Development

# Install dependencies
npm install

# Run development server with demo
npm run dev:demo

# Run tests
npm test

# Run e2e tests
npm run test:e2e

# Build library
npm run build:lib

# Generate documentation
npm run docs

Scripts

| Script | Description | |--------|-------------| | dev:demo | Start development server with demo app | | build | Build library (validate GLSL + TypeScript + bundle) | | build:lib | Build library only | | build:demo | Build demo application | | test | Run unit tests (watch mode) | | test:run | Run unit tests once | | test:coverage | Run tests with coverage report | | test:e2e | Run Playwright e2e tests | | lint | Lint source code | | format | Format source code | | validate:glsl | Validate GLSL shader syntax | | bench | Run performance benchmarks | | docs | Generate TypeDoc documentation |

License

MIT License - see LICENSE for details.

Contributing

Contributions are welcome! Please read our contributing guidelines before submitting pull requests.

Links