Radar Sweep

A beam sweeps round a radar dish and each blip flashes the moment the beam passes over it, then fades.

  • CSS
  • SVG & shapes
  • Loop
  • Added
Scanning

AI prompt

Create a radar sweep in SVG and CSS. Draw three concentric circle rings and two crosshair lines in a muted stroke. Over them, place a disc with a conic-gradient(from 0deg, accent at 50% opacity, transparent 70deg) that rotates 360deg every 3s linear infinite, so a soft wedge trails behind a bright leading edge. Place three blip dots at known angles. Each blip runs a 3s keyframe (opacity 0 → 1 at 2% → 0 at 40%, scale 1.6 → 1) with animation-delay = (angle / 360) × 3s, so it flashes exactly as the beam passes. Pause all animation when off screen. Under prefers-reduced-motion, hold the beam still and show the blips at half opacity. The caption reads "Scanning".

Source

Demo.tsx
'use client'

import { useEffect, useRef } from 'react'
import s from './demo.module.css'

const PERIOD = 3
// Angles are measured clockwise from 12 o'clock, matching the conic-gradient's start.
const BLIPS = [
  { angle: 50, r: 0.55 },
  { angle: 160, r: 0.8 },
  { angle: 285, r: 0.35 },
]

export default function RadarSweep({ labels }: { labels: Record<string, string> }) {
  const ref = useRef<HTMLDivElement>(null)

  useEffect(() => {
    const el = ref.current
    if (!el) return
    const io = new IntersectionObserver(([e]) => el.toggleAttribute('data-paused', !e.isIntersecting))
    io.observe(el)
    return () => io.disconnect()
  }, [])

  return (
    <div ref={ref} className={s.wrap}>
      <div className={s.radar}>
        <svg viewBox="0 0 100 100" className={s.grid} aria-hidden="true">
          <circle cx="50" cy="50" r="48" />
          <circle cx="50" cy="50" r="32" />
          <circle cx="50" cy="50" r="16" />
          <path d="M50 2v96M2 50h96" />
        </svg>
        <div className={s.beam} />
        {BLIPS.map(({ angle, r }) => {
          const rad = (angle * Math.PI) / 180
          return (
            <span
              key={angle}
              className={s.blip}
              style={{
                left: `${50 + Math.sin(rad) * r * 48}%`,
                top: `${50 - Math.cos(rad) * r * 48}%`,
                animationDelay: `${(angle / 360) * PERIOD}s`,
              }}
            />
          )
        })}
      </div>
      <span className={s.caption}>{labels.caption}</span>
    </div>
  )
}
demo.module.css
.wrap {
  display: grid;
  justify-items: center;
  gap: 12px;
}

.radar {
  position: relative;
  width: 160px;
  aspect-ratio: 1;
  border-radius: 50%;
  overflow: hidden;
}

.grid {
  position: absolute;
  inset: 0;
  fill: none;
  stroke: var(--stage-rule);
  stroke-width: 0.6;
}

.beam {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: conic-gradient(from 0deg, color-mix(in oklch, var(--stage-accent) 50%, transparent), transparent 70deg);
  transform: scaleX(-1);
  animation: spin 3s linear infinite;
}

/* scaleX(-1) puts the bright edge in front of the wedge as it turns clockwise */
@keyframes spin {
  from { transform: scaleX(-1) rotate(0deg); }
  to { transform: scaleX(-1) rotate(-360deg); }
}

.blip {
  position: absolute;
  width: 8px;
  height: 8px;
  margin: -4px 0 0 -4px;
  border-radius: 50%;
  background: var(--stage-accent);
  box-shadow: 0 0 10px var(--stage-accent);
  opacity: 0;
  animation: blip 3s linear infinite backwards;
}

@keyframes blip {
  0% { opacity: 0; transform: scale(1.6); }
  2% { opacity: 1; transform: scale(1.6); }
  40% { opacity: 0; transform: scale(1); }
  100% { opacity: 0; }
}

.wrap[data-paused] .beam,
.wrap[data-paused] .blip { animation-play-state: paused; }

.caption {
  font: 500 var(--text-xs) / 1 var(--font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--stage-muted);
}

@media (prefers-reduced-motion: reduce) {
  .beam, .blip { animation: none; }
  .blip { opacity: 0.5; }
}

Suggest an idea

Seen a motion or a theme you would like to find here? Describe it, and it opens as a prefilled GitHub issue for you to post.

What are you suggesting?

Needs a GitHub account. Suggestions are public.