Components

Black Hole

Animated canvas black hole with particle system and dual cyan+azure mist tendrils.

Black Hole

Animated canvas-based black hole effect with top-down elliptical geometry, 100 particles rising upward out of the void, dual-color mist tendrils (cyan + azure), and a scanline overlay. Uses vandoko oklch design tokens for all colors.

Preview
Open in
Loading preview...

Installation

Terminal
$npx shadcn@latest add @vandoko/blackhole

Usage

import { BlackHole } from "@/components/blackhole";

export default function Page() {
  return (
    <div className="h-screen">
      <BlackHole />
    </div>
  );
}

As a Hero Background

import { BlackHole } from "@/components/blackhole";

export default function HeroSection() {
  return (
    <section className="relative h-[600px]">
      <BlackHole className="absolute inset-0" />
      <div className="relative z-20 flex h-full items-center justify-center">
        <h1 className="text-5xl font-bold text-foreground">Your Headline</h1>
      </div>
    </section>
  );
}

The component runs a 200-particle canvas RAF loop. To keep it out of the initial JS bundle and avoid SSR hydration mismatch on dynamic backgrounds, wrap consumer imports with next/dynamic({ ssr: false }):

import dynamic from "next/dynamic";

const BlackHole = dynamic(
  () =>
    import("@/components/blackhole").then((m) => m.BlackHole),
  { ssr: false },
);

export default function CinematicClose() {
  return (
    <section className="relative min-h-[55vh] md:min-h-[60vh]">
      <BlackHole className="absolute inset-0" />
      <p className="relative z-20 ...">The door opens for those already inside.</p>
    </section>
  );
}

Reduced-Motion Behavior

Under prefers-reduced-motion: reduce, the canvas RAF loop is skipped entirely and the component renders a static gradient fallback (cyan glow + azure base + obsidian vignette) that mirrors the canvas's resting visual rhythm without any animation. No requestAnimationFrame calls, no canvas paint after the first render.

Offscreen Pause

The active canvas pauses its RAF loop when scrolled offscreen via IntersectionObserver and resumes (with lastTime reset to avoid dt spike) on re-entry. CPU/GPU cost drops to zero while the section is not visible.

Features

  • Canvas-based — High-performance 2D rendering with requestAnimationFrame
  • Bulge physics — Rim discs expand outward before converging into the void
  • Particle system — 120 particles with random velocity and opacity
  • Dual mist tendrils — Cyan and azure animated gradients
  • DPI-aware — Crisp on Retina/HiDPI displays
  • Frame-rate independent — Consistent speed across 60/120/144Hz displays
  • Debounced resize — No jank during window resize
  • OffscreenCanvas fallback — Works in older browsers without OffscreenCanvas

Lineage

PropertyValue
Source@vandoko/blackhole
Version0.0.2
Curated2026-03-29
LicenseMIT
DependenciesNone

Modifications

  • Converted from vanilla JSX to typed TypeScript
  • oklch color token migration (cyan + azure accent pair)
  • Tailwind class conversion for overlay elements
  • Added OffscreenCanvas feature detection fallback
  • Frame-rate independent animation with delta-time scaling
  • Debounced resize handler
  • Two-pass disc rendering for minimal save/clip calls
  • prefers-reduced-motion: reduce short-circuits RAF loop and renders static gradient fallback
  • IntersectionObserver pauses RAF when offscreen, resumes (with lastTime reset) on re-entry
  • Documented next/dynamic({ ssr: false }) consumer pattern for SSR-safe lazy loading

On this page