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

weather-animations

v1.1.1

Published

Canvas-powered JavaScript weather backgrounds with TypeScript types, React support, and duotone weather icons.

Readme

Weather Animations

npm version npm downloads license

Canvas-powered JavaScript weather backgrounds with TypeScript types, smooth transitions, configurable intensity, day/night palettes, moon phases, and sunrise, sunset, moonrise, and moonset events.

npm package: weather-animations

Live demo: g3sthousen.github.io/weather-animations

Features

  • Animated weather states for clear, cloudy, overcast, rain, drizzle, showers, freezing rain, sleet, snow, flurries, blizzard, storm, fog, mist, haze, smog, smoke, dust, wind, and hail.
  • Light, medium, and heavy intensity variants.
  • Day and night rendering with condition-specific sky palettes.
  • Subtle/rich fidelity modes for states with extra visual detail.
  • Moon phase rendering and gated celestial events.
  • Core JavaScript API with TypeScript types plus a small React wrapper.
  • Duotone SVG weather icons for the core icon-supported conditions and intensities.
  • Deterministic test hooks for visual and unit testing.

Quick Start

Install dependencies:

npm install

Run the demo:

npm run dev

Build the core, React, and icon library bundles:

npm run build

Build the static demo for GitHub Pages:

npm run build:demo

Run tests:

npm test
npm run test:visual

JavaScript Usage

ES modules:

import { WeatherScene } from 'weather-animations';

const container = document.querySelector('#weather');
const scene = new WeatherScene(container);

scene.set({
  condition: 'rain',
  intensity: 'medium',
  time: 'night',
  fidelity: 'rich',
  transitionMs: 1200,
});

CommonJS:

const { WeatherScene } = require('weather-animations');

const container = document.querySelector('#weather');
const scene = new WeatherScene(container);

scene.set({
  condition: 'showers',
  intensity: 'medium',
  time: 'day',
});

Clean up when the host view unmounts:

scene.destroy();

TypeScript Usage

The package ships generated declaration files for the core API, React wrapper, and icon components.

import { WeatherScene, type WeatherConfig } from 'weather-animations';

const container = document.querySelector<HTMLElement>('#weather');
if (!container) throw new Error('Missing weather container');

const config: WeatherConfig = {
  condition: 'freezing-rain',
  intensity: 'medium',
  time: 'night',
};

new WeatherScene(container).set(config);

React Usage

JavaScript or TypeScript React apps can import the wrapper from weather-animations/react.

import { WeatherBackground } from 'weather-animations/react';

export function App() {
  return (
    <WeatherBackground
      condition="cloudy"
      intensity="light"
      time="day"
      style={{ position: 'absolute', inset: 0 }}
    />
  );
}

Weather Icons

The icon subpackage provides one duotone SVG icon per weather condition — all 20 conditions are covered.

| Icon | Condition | React component | Raw SVG | | --- | --- | --- | --- | | | clear | ClearIcon | weather-animations/icons/clear.svg | | | cloudy | CloudyIcon | weather-animations/icons/cloudy.svg | | | overcast | OvercastIcon | weather-animations/icons/overcast.svg | | | rain | RainIcon | weather-animations/icons/rain.svg | | | drizzle | DrizzleIcon | weather-animations/icons/drizzle.svg | | | showers | ShowersIcon | weather-animations/icons/showers.svg | | | freezing-rain | FreezingRainIcon | weather-animations/icons/freezing-rain.svg | | | sleet | SleetIcon | weather-animations/icons/sleet.svg | | | snow | SnowIcon | weather-animations/icons/snow.svg | | | flurries | FlurriesIcon | weather-animations/icons/flurries.svg | | | blizzard | BlizzardIcon | weather-animations/icons/blizzard.svg | | | storm | StormIcon | weather-animations/icons/storm.svg | | | hail | HailIcon | weather-animations/icons/hail.svg | | | fog | FogIcon | weather-animations/icons/fog.svg | | | mist | MistIcon | weather-animations/icons/mist.svg | | | haze | HazeIcon | weather-animations/icons/haze.svg | | | smog | SmogIcon | weather-animations/icons/smog.svg | | | smoke | SmokeIcon | weather-animations/icons/smoke.svg | | | dust | DustIcon | weather-animations/icons/dust.svg | | | wind | WindIcon | weather-animations/icons/wind.svg |

React components:

import { RainIcon, getIconName } from 'weather-animations/react-icons';

export function WeatherBadge() {
  return (
    <RainIcon
      width={32}
      height={32}
      aria-label="Rain"
      style={{
        '--wi-primary': '#5f7480',
        '--wi-accent': '#38a7ff',
      }}
    />
  );
}

getIconName('rain'); // "rain"

Raw SVG assets are exported from weather-animations/icons/*:

const iconPath = require.resolve('weather-animations/icons/rain.svg');

Icon components use standard SVG props such as width, height, className, style, and ARIA attributes. Colors can be themed with --wi-primary and --wi-accent.

Icons cover every condition: clear, cloudy, overcast, rain, drizzle, showers, freezing-rain, sleet, snow, flurries, blizzard, storm, hail, fog, mist, haze, smog, smoke, dust, and wind. The component name is the PascalCase condition followed by Icon (e.g. FreezingRainIcon).

Configuration

| Option | Values | Default | | --- | --- | --- | | condition | clear, cloudy, overcast, rain, drizzle, showers, freezing-rain, sleet, snow, flurries, blizzard, storm, fog, mist, haze, smog, smoke, dust, wind, hail | Required | | intensity | light, medium, heavy | medium | | time | day, night | day | | fidelity | subtle, rich | subtle | | moonPhase | new, waxing-crescent, first-quarter, waxing-gibbous, full, waning-gibbous, last-quarter, waning-crescent | full | | celestialEvent | none, sunrise, sunset, moonrise, moonset | none | | celestialProgress | Number from 0 to 1 | 0.5 | | transitionMs | Number in milliseconds | 1200 |

Celestial events are normalized by the core renderer. Sunrise and sunset are only shown during day states; moonrise and moonset are only shown during night states. Events are available for clear, wind, and light/medium cloudy skies.

For cloudy, intensity maps to cloud-cover language: light is partly cloudy, medium is mostly cloudy, and heavy is dense cloudy. Use overcast for a fully covered sky.

The demo disables the fidelity switch for conditions where rich has no meaningful visual effect.

Weather API Mapping

The public API is intentionally small so it can be mapped from most weather providers:

  • Map provider weather codes to one of the supported condition values, or use normalizeWeatherInput() with neutral weather fields.
  • Map precipitation, cloud cover, wind speed, or provider severity to light, medium, or heavy.
  • For cloud cover, prefer cloudy/light for partly cloudy, cloudy/medium for mostly cloudy, cloudy/heavy for dense cloudy, and overcast for a closed cloud deck.
  • Map local sunrise/sunset and current time to time and optional celestialEvent.
  • Use provider moon phase data for moonPhase when available.

Provider-neutral helper:

import { normalizeWeatherInput, WeatherScene } from 'weather-animations';

const config = normalizeWeatherInput({
  precipitationType: 'sleet',
  precipitationIntensity: 0.8,
  cloudCover: 100,
  time: 'night',
});

new WeatherScene(container).set(config);

OpenWeather Example

OpenWeather's Current Weather API returns provider-specific condition IDs and fields such as weather[0].id, clouds.all, wind.speed, visibility, rain["1h"], snow["1h"], dt, timezone, sys.sunrise, and sys.sunset. Keep that mapping in your app, then pass the normalized result into the renderer:

import { normalizeWeatherInput, WeatherScene } from 'weather-animations';

function intensityFromMmPerHour(mmPerHour) {
  if (typeof mmPerHour !== 'number') return undefined;
  if (mmPerHour < 1) return 'light';
  if (mmPerHour < 4) return 'medium';
  return 'heavy';
}

function timeFromOpenWeather(openWeather) {
  const timezone = openWeather.timezone ?? 0;
  const current = (openWeather.dt ?? 0) + timezone;
  const sunrise = (openWeather.sys?.sunrise ?? 0) + timezone;
  const sunset = (openWeather.sys?.sunset ?? 0) + timezone;

  return current >= sunrise && current < sunset ? 'day' : 'night';
}

function mapOpenWeatherToWeatherAnimations(openWeather) {
  const weather = openWeather.weather?.[0] ?? {};
  const id = weather.id;
  const description = String(weather.description ?? '').toLowerCase();
  const rain1h = openWeather.rain?.['1h'];
  const snow1h = openWeather.snow?.['1h'];
  const precipitationAmount = rain1h ?? snow1h;
  const baseInput = {
    windSpeed: openWeather.wind?.speed,
    visibility: openWeather.visibility,
    precipitationIntensity: intensityFromMmPerHour(precipitationAmount),
    time: timeFromOpenWeather(openWeather),
  };

  if (id >= 200 && id < 300) {
    return normalizeWeatherInput({ ...baseInput, thunderstorm: true });
  } else if (id >= 300 && id < 400) {
    return normalizeWeatherInput({ ...baseInput, precipitationType: 'drizzle' });
  } else if (id >= 500 && id < 600) {
    if (id === 511 || description.includes('freezing')) {
      return normalizeWeatherInput({ ...baseInput, precipitationType: 'freezing-rain' });
    }
    if (id >= 520 || description.includes('shower')) {
      return normalizeWeatherInput({ ...baseInput, phenomenon: 'showers' });
    }
    return normalizeWeatherInput({ ...baseInput, precipitationType: 'rain' });
  } else if (id >= 600 && id < 700) {
    if ([611, 612, 613, 615, 616].includes(id)) {
      return normalizeWeatherInput({ ...baseInput, precipitationType: 'sleet' });
    }
    if (id >= 620 || description.includes('flurr')) {
      return normalizeWeatherInput({ ...baseInput, phenomenon: 'flurries' });
    }
    return normalizeWeatherInput({ ...baseInput, precipitationType: 'snow' });
  } else if (id >= 700 && id < 800) {
    if (id === 701) return normalizeWeatherInput({ ...baseInput, visibility: 'mist' });
    if (id === 711) return normalizeWeatherInput({ ...baseInput, visibility: 'smoke' });
    if (id === 721) return normalizeWeatherInput({ ...baseInput, visibility: 'haze' });
    if (id === 741) return normalizeWeatherInput({ ...baseInput, visibility: 'fog' });
    if ([731, 751, 761, 762].includes(id)) {
      return normalizeWeatherInput({ ...baseInput, visibility: 'dust' });
    }
  } else if (id === 800) {
    return normalizeWeatherInput({
      ...baseInput,
      cloudCover: openWeather.clouds?.all,
      phenomenon: 'clear',
    });
  } else if (id >= 801 && id <= 804) {
    return normalizeWeatherInput({
      ...baseInput,
      cloudCover: openWeather.clouds?.all,
    });
  }

  return normalizeWeatherInput({
    ...baseInput,
    cloudCover: openWeather.clouds?.all,
  });
}

const weatherConfig = mapOpenWeatherToWeatherAnimations(openWeatherResponse);
new WeatherScene(container).set(weatherConfig);

This is a starting point, not universal meteorological truth. Tune the thresholds and rare-condition mappings for your app, region, and OpenWeather product. See the OpenWeather Current Weather API and OpenWeather condition codes for the source fields.

Keep provider-specific code tables outside the renderer, then pass the normalized result into WeatherScene.set() or WeatherBackground.

Development

npm test
./node_modules/.bin/tsc --noEmit --skipLibCheck
npm run build
npm run test:visual

Visual tests use Playwright snapshots in tests/visual.

Contributing

Issues and pull requests are welcome. For larger changes, please open an issue first so the scope and API impact can be discussed before implementation.

Before opening a pull request:

  • Keep the change focused and explain the user-facing impact.
  • Run npm test, ./node_modules/.bin/tsc --noEmit --skipLibCheck, and npm run build.
  • Include visual notes or screenshots for rendering changes.

External pull requests are reviewed by the maintainer. Only the maintainer approves and merges changes into main.