AtmosphereFree

Starfield

200 stars in 3D perspective converge toward the eye — semi-transparent fill creates a warp-speed trail in pure 2D canvas, no library needed.

JSCanvasStars
Hover or click the scene to interact

This effect is free — the Atmosphere category contains 57 effects total, including 10 free. Effect.Labs has 811 vanilla effects. Explore the category →

Usage examples

① Hero / Landing — Tech or SaaS product launch page

WhenLaunching a space-themed platform, a data tool, or a dev product — the starfield is the main fold background, visible before any scroll.
WhyStars rushing toward the viewer create immersive depth without drowning the headline: white pinpoints on near-black form a high-contrast backdrop that never distracts. No external image, zero network request.
SettingsSpeed z − 2 px/frame (default), 200 stars, trail opacity 0.2, max radius 2.5 px. Add a CSS scrim (rgba(0,0,0,0.35)) in absolute position over the canvas to guarantee WCAG AA text contrast.

② Product component — Loading card in a dashboard

WhenA dashboard module awaits data (import in progress, slow API, incomplete onboarding) — the card fills the space with a meaningful animation during resolution.
WhyThe starfield suggests the system is actively 'searching', reducing wait anxiety. It's native 2D canvas: no lib, no GIF, zero additional network request.
SettingsReduced container (360 × 200 px), change s.z -= 2 to s.z -= 1.5 for a gentler pace, trail opacity 0.15 for an ethereal look. Glassmorphism card overlay (background: rgba(15,15,22,0.82); backdrop-filter: blur(8px)).

③ Game splash screen — Full-viewport intro for a sci-fi game

WhenA sci-fi game or astronomy app is starting — the loading screen precedes the session and must hold attention while assets initialise.
WhyThe Starfield fully leverages its convergent 3D perspective over a large surface: the warp tunnel reads instantly, long trails amplify the sense of hyperspace propulsion, and the near-black background is the effect's native environment — no compromise, no artificial staging.
SettingsSpeed z − 3 to 4 px/frame, trail opacity 0.15–0.2 (long trails), canvas at full viewport (height: 100vh). White-star-on-near-black contrast naturally exceeds 7:1 — no scrim required for bold headings.

How it works

The effect relies on perspective projection (pinhole camera model): each star is a 3D point (x, y, z) centered on the Z axis. Every frame, z decreases by 2, simulating movement toward the camera. Screen position follows sx = (x/z) × (w/2) + w/2 and sy = (y/z) × (h/2) + h/2 — the smaller z (closer star), the further sx and sy diverge from center, producing the characteristic tunnel expansion.

The speed trails are not a computed motion blur but a semi-transparent fill at rgba(10,10,15,0.2) before each redraw: the background is painted at 20% opacity, leaving previous frames faintly visible. Near stars (small z) move fast in pixels and therefore accumulate more ghost images — trails lengthen naturally without extra computation.

Each star's size and brightness scale inversely with z: radius r = max(0.5, (1 − z/w) × 2.5), alpha a = 1 − z/w. A far star (z ≈ w) is a 0.5 px near-invisible dot; at the foreground (z → 0) it reaches 2.5 px pure white. When z ≤ 0, the star resets to z = w with a new random x, y, maintaining a continuous stream of 200 particles without dynamic allocation.

The loop runs via a simple recursive requestAnimationFrame — there is no IntersectionObserver to cut the RAF off-screen, and no prefers-reduced-motion check. Canvas dimensions are read from the parent via getBoundingClientRect() at init only: no ResizeObserver, the canvas does not recalibrate on resize.

Accessibility

  • prefers-reduced-motion: not handled. The code does not check window.matchMedia('(prefers-reduced-motion: reduce)') — the animation loops regardless of system preference. For WCAG 2.3.3 compliance, add a guard at the top of the IIFE and stop the RAF if the preference is active.
  • The <canvas> has no aria-hidden="true" in the delivered code — screen readers may attempt to explore it without any useful description. Fix: add aria-hidden="true" to the canvas and role="img" aria-label="Animated starfield" to the wrapper.
  • No pause mechanism is exposed: the animation starts automatically and loops with no Pause button, failing WCAG 2.2.2 for auto-started animations lasting more than 5 seconds.
  • Star contrast: rgba(255,255,255,a) against rgb(10,10,15) yields a ratio above 7:1 for foreground stars. No text is rendered on the canvas — overlay text must be managed entirely in the HTML layer.
  • No keyboard or pointer interaction in the effect: the canvas is purely decorative, no focus trap, no keyboard events.

Browser compatibility

Requires Canvas 2D Context and requestAnimationFrame — available in all modern browsers since 2012. Zero external dependencies.

Chrome 4+✓ Full
Firefox 3.6+✓ Full
Safari 3.1+✓ Full
Edge 12+✓ Full
Mobile iOS✓ Full
Android Chrome✓ Full

If <code>canvas</code> is not supported (very old browsers), the parent <code>&lt;div&gt;</code> displays empty with the dark CSS background — no fatal JS errors, HTML overlay content stays readable.

The code

Copy the three blocks into your page. No dependencies.

HTML
<div class="ps-canvas-wrap">
  <canvas class="ps-canvas" id="psStarfield" width="392" height="280">
  </canvas>
</div>
CSS
.ps-canvas  {
   width: 100%;
   height: 280px;
   display: block;
   border-radius: 0px;
   }

.ps-canvas-wrap  {
   position: relative;
   width: 100%;
   height: 280px;
   }

.ps-canvas-wrap canvas  {
   position: absolute;
   top: 0px;
   left: 0px;
   width: 100%;
   height: 100%;
   }
JavaScript (fx-0485)
(function() {
  const canvas = document.getElementById('psStarfield');
  if (!canvas) return;
  const ctx = canvas.getContext('2d');
  const rect = canvas.parentElement.getBoundingClientRect();
  canvas.width = rect.width;
  canvas.height = rect.height;
  const w = canvas.width, h = canvas.height;
  const stars = [];
  for (let i = 0; i < 200; i++) {
    stars.push({ x: Math.random() * w - w / 2, y: Math.random() * h - h / 2, z: Math.random() * w });
  }
  function draw() {
    ctx.fillStyle = 'rgba(10,10,15,0.2)';
    ctx.fillRect(0, 0, w, h);
    stars.forEach(s => {
      s.z -= 2;
      if (s.z <= 0) { s.z = w; s.x = Math.random() * w - w / 2; s.y = Math.random() * h - h / 2; }
      const sx = (s.x / s.z) * w / 2 + w / 2;
      const sy = (s.y / s.z) * h / 2 + h / 2;
      const r = Math.max(0.5, (1 - s.z / w) * 2.5);
      const a = 1 - s.z / w;
      ctx.beginPath();
      ctx.arc(sx, sy, r, 0, Math.PI * 2);
      ctx.fillStyle = `rgba(255,255,255,${a})`;
      ctx.fill();
    });
    requestAnimationFrame(draw);
  }
  draw();
})();

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Star count (JS init: 200) 200 Size of the stars[] array. 80–120 for a calm sky, 300–400 for galactic density — watch performance on mobile above 300.
Speed (JS: s.z -= 2) 2 px/frame z decrement per frame. 1 = contemplative, 2 = default warp, 4–5 = hyperdrive. Extract to a global variable (window.sfSpeed) for runtime control.
Max star size (JS: × 2.5) 2.5 Factor in (1 − z/w) × 2.5. Increase to 4 for larger foreground stars, reduce to 1.5 for a finer stellar look.
Trail opacity (JS: rgba …, 0.2) 0.2 Alpha of the persistence fill. 0.1 = long spectral trails, 0.4 = short crisp trails, 1.0 = no trail at all (pure stars, no speed effect).
Star color (JS: rgba(255,255,255,…)) white Replace 255,255,255 with 200,220,255 for a galactic blue tint or 255,220,180 for a warm amber glow.
Background color (CSS: rgb(10,10,15)) rgb(10,10,15) CSS color of the wrapper and the JS persistence fillStyle — change both together: rgb(15,5,25) = night purple, rgb(0,10,20) = deep navy.
.ps-canvas-wrap height (CSS) 280px Canvas stretches to parent width but height is fixed in CSS. Switch to 100vh for a full-screen hero or 400px for a taller banner.

FAQ

Can the starfield speed be changed at runtime without reloading the page?

Not natively — the IIFE closes over the speed constant (s.z -= 2). For runtime control, extract it before copying: add window.sfSpeed = 2 at the top of the IIFE and replace s.z -= 2 with s.z -= window.sfSpeed. You can then write window.sfSpeed = 4 from any callback to accelerate on the fly.

Why do all stars emerge from the center rather than random positions?

This is the perspective projection mechanic: at large depth (z → w), sx and sy mathematically converge to the canvas center (w/2, h/2). When a star resets to z = w, it reappears imperceptibly close to center and gradually spreads outward — that is the correct tunnel effect, not a bug.

Does the effect work inside an iframe or a web component with shadow DOM?

In an iframe, yes without modification — getElementById('psStarfield') is local to the iframe document. In a web component with shadow DOM, replace getElementById with this.shadowRoot.getElementById to keep the lookup inside the shadow tree. The IIFE is fully isolated and touches nothing outside its own canvas.