← SVG & icons

Animated Weather Icons

Six weather icons, each with its own little loop: the sun’s rays turn and breathe, clouds drift, rain falls, snowflakes tumble and lightning flickers out of a dark cloud.

Sun
Partly cloudy
Cloud
Rain
Snow
Storm

How to use it

  1. Paste the snippet just before </head>.
  2. Add data-animated-weather-icons to an empty <span>, set to the icon you want: sun, partly, cloud, rain, snow or storm.
  3. Size it with font-size: the icon is 1em square.

Options

Add any of these attributes to the same element.

data-speedSpeed up or slow down the loop: 2 is twice as fast. Default 1.

Each icon gets an aria-label naming the weather; set your own (“Rain, 12°C”) if you like. The loops pause while off screen, and stay still with reduced motion.

1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Animated Weather Icons: sun, cloud, rain, snow, storm and partly cloudy
    // icons, each with its own little loop. The sun's rays turn and breathe,
    // clouds drift, rain falls, snowflakes tumble and lightning flickers.
    // Usage: <span data-animated-weather-icons="rain"></span>
    //   Icons: sun, cloud, rain, snow, storm, partly. Size it with font-size
    //   (the icon is 1em square).
    // Optional: data-speed (a multiplier, 2 is twice as fast; default 1).
    // The loops pause while off screen, and stay still with reduced motion.
    var LABELS = { sun: 'Sunny', cloud: 'Cloudy', rain: 'Rain', snow: 'Snow', storm: 'Thunderstorm', partly: 'Partly cloudy' };
    var NS = 'http://www.w3.org/2000/svg';

    var css =
      '.awi{display:inline-block;width:1em;height:1em;line-height:0;vertical-align:middle}' +
      '.awi svg{display:block;width:100%;height:100%;overflow:visible}' +
      '.awi svg [class]{transform-box:fill-box;transform-origin:center}' +
      '.awi-off *{animation-play-state:paused!important}' +
      '.awi-spin{animation:awi-spin 14s linear infinite}' +
      '.awi-breathe{animation:awi-breathe 1.8s ease-in-out infinite alternate}' +
      '.awi-core{animation:awi-breathe 2.4s ease-in-out infinite alternate}' +
      '.awi-drift{animation:awi-drift 3.2s ease-in-out infinite alternate}' +
      '.awi-drift2{animation:awi-drift 4.4s ease-in-out infinite alternate-reverse}' +
      '.awi-drop{animation:awi-fall .9s linear infinite}' +
      '.awi-flake{animation:awi-snow 2.8s linear infinite}' +
      '.awi-bolt{animation:awi-bolt 3s linear infinite}' +
      '.awi-flash{animation:awi-flash 3s linear infinite}' +
      '.awi-cloud{filter:drop-shadow(0 1.5px 2px rgba(30,41,59,.28))}' +
      '@keyframes awi-spin{to{transform:rotate(360deg)}}' +
      '@keyframes awi-breathe{from{transform:scale(.94)}to{transform:scale(1.06)}}' +
      '@keyframes awi-drift{from{transform:translateX(-2px)}to{transform:translateX(2.5px)}}' +
      '@keyframes awi-fall{0%{transform:translate(1px,-4px);opacity:0}25%{opacity:1}100%{transform:translate(-3px,12px);opacity:0}}' +
      '@keyframes awi-snow{0%{transform:translate(0,-4px) rotate(0);opacity:0}20%{opacity:1}50%{transform:translate(2.5px,4px) rotate(90deg)}80%{opacity:1}100%{transform:translate(-1.5px,13px) rotate(180deg);opacity:0}}' +
      '@keyframes awi-bolt{0%,2%{opacity:0}5%{opacity:1}9%{opacity:.25}13%{opacity:1}55%{opacity:1}68%,100%{opacity:0}}' +
      '@keyframes awi-flash{0%,2%,20%,100%{filter:brightness(1)}5%,13%{filter:brightness(1.35)}9%{filter:brightness(1.1)}}' +
      '@media (prefers-reduced-motion:reduce){.awi svg *{animation:none!important}}';
    var style = document.createElement('style');
    style.textContent = css;
    document.head.appendChild(style);

    // A cloud as a union of circles, spanning roughly x 14–52, y 20–46.
    function cloud(fill, cls) {
      return '<g class="' + cls + '" fill="' + fill + '"><circle cx="22" cy="38" r="8"/><circle cx="33" cy="31" r="11"/>' +
        '<circle cx="44" cy="38" r="8"/><rect x="14" y="36" width="38" height="10" rx="5"/></g>';
    }

    function sun() {
      var rays = '';
      for (var i = 0; i < 8; i++) {
        var a = i * Math.PI / 4, c = Math.cos(a), s = Math.sin(a);
        rays += '<line x1="' + (32 + c * 16).toFixed(2) + '" y1="' + (32 + s * 16).toFixed(2) + '" x2="' + (32 + c * 22).toFixed(2) + '" y2="' + (32 + s * 22).toFixed(2) + '"/>';
      }
      return '<g class="awi-spin"><g class="awi-breathe" stroke="#f59e0b" stroke-width="3.6" stroke-linecap="round">' +
        '<circle cx="32" cy="32" r="22" fill="none" stroke="none"/>' + rays + '</g></g>' +
        '<circle class="awi-core" cx="32" cy="32" r="11" fill="#fbbf24"/>';
    }

    function drops(color, xs) {
      return xs.map(function (x, i) {
        return '<line class="awi-drop" style="animation-delay:' + -(i * 0.9 / xs.length).toFixed(2) + 's" x1="' + x + '" y1="42" x2="' + (x - 2) + '" y2="48" stroke="' + color + '" stroke-width="3" stroke-linecap="round"/>';
      }).join('');
    }

    function flakes(xs) {
      return xs.map(function (x, i) {
        return '<g transform="translate(' + x + ' ' + (44 + (i % 2) * 3) + ')"><g class="awi-flake" style="animation-delay:' + -(i * 2.8 / xs.length).toFixed(2) + 's" stroke="#60a5fa" stroke-width="1.7" stroke-linecap="round">' +
          '<path d="M0 -3.2 V3.2 M-2.8 -1.6 L2.8 1.6 M-2.8 1.6 L2.8 -1.6"/></g></g>';
      }).join('');
    }

    var ICONS = {
      sun: sun,
      cloud: function () {
        return '<g transform="translate(9 -7) scale(.8)">' + cloud('#a8b6cb', 'awi-cloud awi-drift2') + '</g>' +
          '<g transform="translate(-2 4)">' + cloud('#fff', 'awi-cloud awi-drift') + '</g>';
      },
      rain: function () {
        return drops('#3b82f6', [21, 31, 41, 26, 36, 46]) +
          '<g transform="translate(0 -9)">' + cloud('#cbd5e1', 'awi-cloud awi-drift') + '</g>';
      },
      snow: function () {
        return flakes([20, 28, 36, 44, 24, 40]) +
          '<g transform="translate(0 -9)">' + cloud('#e2e8f0', 'awi-cloud awi-drift') + '</g>';
      },
      storm: function () {
        return drops('#60a5fa', [19, 45, 24, 40]) +
          '<path class="awi-bolt" d="M35 36 L26 50 H32.5 L29 61 L40 46 H33.5 L37 36 Z" fill="#facc15" stroke="#eab308" stroke-width="1" stroke-linejoin="round"/>' +
          '<g transform="translate(0 -9)"><g class="awi-flash">' + cloud('#64748b', 'awi-cloud awi-drift') + '</g></g>';
      },
      partly: function () {
        return '<g transform="translate(-6 -6) scale(.78)">' + sun() + '</g>' +
          '<g transform="translate(4 6)">' + cloud('#fff', 'awi-cloud awi-drift') + '</g>';
      }
    };

    function setup(el) {
      var name = el.getAttribute('data-animated-weather-icons') || 'sun';
      if (!ICONS[name]) name = 'sun';
      el.classList.add('awi', 'awi-off');
      var svg = document.createElementNS(NS, 'svg');
      svg.setAttribute('viewBox', '0 0 64 64');
      svg.setAttribute('aria-hidden', 'true');
      svg.innerHTML = ICONS[name]();
      el.appendChild(svg);
      var speed = +el.dataset.speed;
      if (speed > 0) {
        Array.prototype.forEach.call(svg.querySelectorAll('[class]'), function (n) {
          var cs = getComputedStyle(n);
          var dur = parseFloat(cs.animationDuration);
          if (!dur) return;
          n.style.animationDuration = dur / speed + 's';
          n.style.animationDelay = parseFloat(cs.animationDelay) / speed + 's';
        });
      }
      if (!el.getAttribute('aria-label') && !el.closest('[aria-hidden="true"]')) {
        el.setAttribute('role', 'img');
        el.setAttribute('aria-label', LABELS[name]);
      }
    }

    function init() {
      // Loops only run while the icon is on screen.
      var io = new IntersectionObserver(function (entries) {
        entries.forEach(function (e) { e.target.classList.toggle('awi-off', !e.isIntersecting); });
      });
      document.querySelectorAll('[data-animated-weather-icons]').forEach(function (el) {
        if (el._awi) return;
        el._awi = true;
        setup(el);
        io.observe(el);
      });
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<span data-animated-weather-icons="rain" style="font-size: 64px"></span>
<span data-animated-weather-icons="sun" aria-label="Sunny, 24°C" style="font-size: 64px"></span>