Pulsar

Orbit Stagger

GSAP

A pulsing purple core with three dots orbiting at staggered speeds, all returning to rest together.

Tier
GSAP
Dependencies
gsap
Duration
6 s, loops
Categories
Icons and micro-interactions
Use cases
Loading and waiting, AI and processing
Industries
SaaS and software, Cross-industry
Style
Duotone · none stroke
Set
nebula
Tags
orbitstaggerloopbrandcircle

Companion assets

The same artwork, not animated. Derived from the entry’s own resting state, so it matches exactly.

Animated
GSAP tier
Static vector
Orbit Stagger, not animated
Download SVG

Code

animation.svg

<svg xmlns="http://www.w3.org/2000/svg" aria-labelledby="orbit-stagger-title" class="pulsar pulsar-orbit-stagger" data-pulsar="orbit-stagger" role="img" viewBox="0 0 100 100"><title id="orbit-stagger-title">Orbit stagger</title><defs><radialGradient id="orbit-stagger-core-grad" cx="50%" cy="50%" r="50%"><stop offset="0" stop-color="#a27ffe"></stop><stop offset=".55" stop-color="#7a46ff"></stop><stop offset="1" stop-color="#5430af"></stop></radialGradient></defs><g id="orbit-stagger-orbit-1"><circle id="orbit-stagger-ring-1" cx="50" cy="50" r="16" fill="none" stroke="#5430af" stroke-width=".75" opacity=".55"></circle><circle id="orbit-stagger-dot-1" cx="50" cy="34" r="2.2" fill="#a27ffe"></circle></g><g id="orbit-stagger-orbit-2"><circle id="orbit-stagger-ring-2" cx="50" cy="50" r="26" fill="none" stroke="#5430af" stroke-width=".75" opacity=".45"></circle><circle id="orbit-stagger-dot-2" cx="76" cy="50" r="2.6" fill="#7a46ff"></circle></g><g id="orbit-stagger-orbit-3"><circle id="orbit-stagger-ring-3" cx="50" cy="50" r="36" fill="none" stroke="#5430af" stroke-width=".75" opacity=".35"></circle><circle id="orbit-stagger-dot-3" cx="24.54" cy="75.46" r="2" fill="#5430af"></circle></g><circle id="orbit-stagger-halo" cx="50" cy="50" r="10" fill="#a27ffe" opacity=".22"></circle><circle id="orbit-stagger-core" cx="50" cy="50" r="6" fill="url(#orbit-stagger-core-grad)"></circle></svg>

animation.js

;(function () {
  var el = document.querySelector('[data-pulsar="orbit-stagger"]')
  if (!el || typeof gsap === 'undefined') return

  var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches

  var orbits = [
    el.querySelector('#orbit-stagger-orbit-1'),
    el.querySelector('#orbit-stagger-orbit-2'),
    el.querySelector('#orbit-stagger-orbit-3'),
  ]
  var core = el.querySelector('#orbit-stagger-core')
  var halo = el.querySelector('#orbit-stagger-halo')

  // One cycle is 6 s. Each orbit group holds its ring and its dot, so
  // '50% 50%' of the group is the ring centre. Inner ring turns three
  // times, middle twice, outer once, so all return to rest at exactly 6 s.
  var tl = gsap.timeline({ repeat: -1, paused: reduced })

  var turns = [3, 2, 1]
  for (var i = 0; i < orbits.length; i++) {
    if (!orbits[i]) continue
    tl.to(
      orbits[i],
      { rotation: 360 * turns[i], transformOrigin: '50% 50%', ease: 'none', duration: 6 },
      0,
    )
  }

  if (core) {
    tl.to(
      core,
      {
        scale: 1.12,
        transformOrigin: '50% 50%',
        ease: 'sine.inOut',
        duration: 3,
        yoyo: true,
        repeat: 1,
      },
      0,
    )
  }
  if (halo) {
    tl.to(
      halo,
      {
        scale: 1.3,
        opacity: 0.1,
        transformOrigin: '50% 50%',
        ease: 'sine.inOut',
        duration: 3,
        yoyo: true,
        repeat: 1,
      },
      0,
    )
  }

  el.pulsar = {
    play: function () {
      if (!reduced) tl.play()
    },
    pause: function () {
      tl.pause()
    },
    seek: function (ms) {
      if (!reduced) tl.seek(ms / 1000)
    },
    destroy: function () {
      tl.kill()
    },
  }
})()

From this family3 in Nebula

See the family