Orbit

transform

Everything after the first part circles the centre of the canvas, forever. One shared pivot, so here the canvas is the right reference box, unlike a part turning about itself.

Demo shape

Every animation on the page redraws with the shape you pick.

Align
Ease
Duration
s
0.13
Stagger
s
00.2
Delay
s
02
<!-- 1. Markup -->
<svg viewBox="0 0 120 120" class="k-orbit" aria-hidden="true">
  <circle cx="60" cy="60" r="34" fill="none" stroke="currentColor" stroke-width="8" vector-effect="non-scaling-stroke" />
  <line x1="60" y1="12" x2="60" y2="38" stroke="currentColor" stroke-width="8" stroke-linecap="round" vector-effect="non-scaling-stroke" />
  <line x1="60" y1="82" x2="60" y2="108" stroke="currentColor" stroke-width="8" stroke-linecap="round" vector-effect="non-scaling-stroke" />
  <circle cx="60" cy="60" r="10" fill="currentColor" />
</svg>

// 2. JS
function orbit(svg) {
  if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return () => {}

  const [, ...orbiters] = paths(svg)
  const box = svg.viewBox.baseVal
  // One pivot shared by every orbiter: the centre of the canvas. view-box is
  // the right reference for that, and its origin is the user-space origin,
  // which is why the centre is given in the drawing's own coordinates.
  orbiters.forEach((part) => {
    part.style.transformBox = 'view-box'
    part.style.transformOrigin = `${box.x + box.width / 2}px ${box.y + box.height / 2}px`
  })
  const animations = orbiters.map((part) =>
    part.animate([{ transform: 'rotate(0deg)' }, { transform: 'rotate(360deg)' }], {
      duration: 6 * 1000,
      iterations: Infinity,
      easing: 'linear',
    }),
  )

  return () => {
    animations.forEach((a) => a.cancel())
    orbiters.forEach((part) => {
      part.style.transformBox = ''
      part.style.transformOrigin = ''
    })
  }
}

function paths(svg) {
  return [...svg.querySelectorAll('path, line, polyline, polygon, circle, ellipse, rect')].filter(
    (el) => !el.closest('defs, clipPath, mask'),
  )
}


orbit(document.querySelector('.k-orbit'))