Zum Inhalt springen
UI Kit
Accent

Bereiche

3D-Flächen, Szenen und Hintergründe

Tiefe für deine Seiten in zwei Techniken. CSS-Perspektive für Geräte, Karten und Scroll-Szenen, kleine WebGL-Shader für lebendige Hintergründe. Beide starten im sichtbaren Bereich, ruhen außerhalb und zeigen allen Besuchern ein Standbild.

Auf dieser Seite

CSS-3D oder WebGL#

Das Kit baut Tiefe auf zwei Wegen. CSS-3D stellt echte Inhalte in den Raum, deine Screens, Karten und Texte bleiben dabei HTML, scharf, auswählbar und klickbar. WebGL malt ein Bild hinter den Inhalt, Licht, Partikel oder eine Kugel aus Punkten, Pixel für Pixel mit einem kleinen Shader.

Nimm CSS-3D, wenn der Inhalt selbst die Tiefe trägt, etwa ein Produkt-Screen im gedrehten Gerät oder Preiskarten, die sich zum Zeiger neigen. Die 3D-Flächen stehen dafür still im Raum, die 3D-Szenen bewegen sich beim Scrollen.

ComponentVariantenWofür
card-stack-3dfan, deck, cascade, ring, drumKarten in echter Tiefe, ein Fächer in der Hand, ein Stapel zum Austeilen, eine Kaskade aus Fenstern, ein drehender Ring und eine Trommel zum Durchblättern. Echtes HTML auf jeder Karte, Pfeiltasten überall, der Ring hält bei Hover und Fokus an.
device-3dphone, laptop, tablet, browser, anglesEin Telefon, Tablet, Laptop oder Browserfenster in echter CSS-Perspektive, mit Kante und Rückseite. Es ruht in einem Kamerawinkel, neigt sich zum Zeiger und schwebt sanft, solange es zu sehen ist, dein Inhalt bleibt darin bedienbar.
flip-cardteam, facts, product, stats, giftKarten mit Vorder- und Rückseite, die sich in 3D umdrehen, per Hover, Klick, Tippen oder Tastatur. Die verdeckte Seite ist inert, beide Seiten teilen sich eine Zelle, nichts springt, und bei reduzierter Bewegung wird überblendet.
tilt-cardfeatures, product, profile, pricing, ticketKarten, die sich wie Glas zum Zeiger neigen, mit einem Licht, das folgt, und Inhalten in eigener Tiefe. Auch der Fokus hebt sie an, auf Touchscreens und bei reduzierter Bewegung liegen sie ruhig und flach.
depth-layersinterface, scroll, diorama, inspectorEin Bild, das sich bei Hover, Fokus, per Knopf oder beim Scrollen in seine Ebenen auffächert. Jede Ebene behält ihren Namen als echten Text daneben, ohne JavaScript öffnet sich der Fächer trotzdem beim Hover.
isometric-stagedashboard, stack, page, workflowEin Produkt oder System als Platten in einem isometrischen Raum, in seine Ebenen auseinandergehoben. Eine Legende daneben hebt bei Hover oder Fokus eine Platte an, ein Knopf setzt die Bühne zusammen und nimmt sie auseinander.
ComponentVariantenWofür
depth-textextrude, layers, rise, echo, crawlSchrift in 3D-Ebenen, die sich mit dem Scroll dreht, aufrichtet und auseinandergeht. Die Wörter bleiben eine echte Überschrift, die Tiefe kommt aus Kopien, die Screenreader nicht sehen. Ohne JavaScript steht das fertige Bild.
camera-flightchapters, gallery, timeline, wordsEine Kamera, die beim Scrollen durch Ebenen voller Inhalt nach vorn fliegt. Jede Ebene kommt aus dem Dunst, wird vor dir scharf und zieht vorbei. Ohne JavaScript stehen die Ebenen als ruhige Liste da.
floating-panelsfeatures, shelf, orbit, splitPaneele, die im Raum schweben und beim Scrollen nacheinander nach vorn kommen. Das vordere dreht sich zu dir, die anderen warten im Raum. Ohne JavaScript stehen sie in einem ruhigen Raster.
perspective-floorgrid, stage, bars, horizonEin Boden aus Linien oder Punkten bis zu einem weichen Horizont, als Bühne für eine Überschrift, Karten, Zahlen oder ein Angebot. Er strömt beim Scrollen auf dich zu und treibt langsam, solange er zu sehen ist, Text steht immer auf ruhigem Grund.
rotate-showcasephone, laptop, cube, cardEin Objekt, das sich mit dem Scroll dreht und bei jeder Drehung eine neue Seite zeigt, mit dem passenden Kapitel daneben. Die Bildschirme wechseln, während die Rückseite zu sehen ist, den Wechsel selbst sieht man nie.
tunnel-zoomcards, frames, rings, portalEin Zoom durch einen Tunnel beim Scrollen, ausgekleidet mit Karten, gerahmten Bildern, Ringen mit Zahlen oder einem Tor aus Licht. Die Sektion bleibt stehen, die Kamera fährt, am Ende wartet ein Schlusstext.

Nimm WebGL, wenn das Bild die Bühne für eine Aussage ist, hinter einem Hero, einem Band mit Zahlen oder dem Call to Action am Ende. Die 3D-Hintergründe nehmen deinen Inhalt als Children und legen ihn über das Bild.

ComponentVariantenWofür
dot-spheresplit, horizon, card, ctaEine drehende Kugel aus gleichmäßig verteilten Punkten, mit WebGL berechnet. Helle Vorderseite, schwache Rückseite, einige Punkte leuchten im Akzent, ein Lichtkranz umgibt sie. Neigt sich mit dem Zeiger.
glow-orbsplit, rise, card, ctaEine weiche, atmende Lichtkugel, wie eine Kugel schattiert, mit Wirbel im Inneren und weitem Schein, mit WebGL gezeichnet. Neigt sich zum Zeiger.
light-rayshero, band, card, ctaLichtschächte von oben mit Dunst und schwebendem Staub, mit WebGL gezeichnet. Sie schimmern, ohne sich zu wiederholen, die Quelle folgt leicht dem Zeiger.
particle-fieldhero, band, card, ctaLeuchtende Partikel in bis zu sechs Tiefenebenen, mit WebGL gezeichnet. Nahe groß und weich, ferne fein, der Zeiger fächert die Ebenen auf und erhellt seine Umgebung.
shader-gradienthero, band, card, ctaFließendes Licht in der Akzentfarbe und ihren Nachbarn, live mit WebGL gezeichnet. Faltet sich wie Seide, folgt Zeiger und Scrollen, mit Standbild als Rückfall.
wave-fieldhero, band, card, ctaEine wellige Fläche in Perspektive, mit WebGL als Linien oder Punkte gezeichnet. Gleitet auf dich zu, Kämme fangen den Akzent, der Zeiger hebt die Wellen unter sich.

Installieren#

Jede 3D-Component installierst du wie jede andere. aiw add holt die Helfer als Abhängigkeit mit, depth für die CSS-Flächen und webgl für die Hintergründe. Mehr braucht es nicht, die beiden Helfer sind selbst geschrieben und kommen ohne three.js aus.

aiw add @ai-website/device-3d @ai-website/shader-gradient
ShaderGradient, Variante heroIn Components ansehen
import { ShaderGradient } from '@/components/ui/shader-gradient';
import { Device3D } from '@/components/ui/device-3d';

<ShaderGradient scrim="left" scrimStrength={0.7} className="min-h-[560px]">
  <div className="kit-container grid items-center gap-12 py-24 md:grid-cols-2">
    <div>
      <h1>Dein Konto, in einer Hand</h1>
      <p>Überweisen, sparen und teilen, mit einem Blick auf den ganzen Monat.</p>
    </div>
    <Device3D device="phone" angle="hero" width={280} label="Banking-App auf einem Telefon">
      <img src="/app-home.avif" alt="Startscreen der App" width={390} height={844} />
    </Device3D>
  </div>
</ShaderGradient>

In Astro bekommt eine 3D-Component eine client-Direktive, damit sie läuft. client:visible passt fast immer, die Bewegung startet ohnehin erst im sichtbaren Bereich.

src/pages/index.astro
<!-- Ohne Direktive steht das Standbild da, fertig und ruhig -->
<ShaderGradient scrim="center">…</ShaderGradient>

<!-- Mit Direktive startet der Shader, sobald die Section ins Bild kommt -->
<ShaderGradient client:visible scrim="center">…</ShaderGradient>

Der WebGL-Helfer#

webgl ist ein schlanker Helfer ohne Bibliothek. Ein Dreieck deckt den Canvas ab, der Fragment-Shader malt jedes Pixel. Alles drumherum übernimmt der Helfer, also Größe mit gedeckelter Pixeldichte, Start im sichtbaren Bereich, Pause außerhalb und im verborgenen Tab, Theme- und Accent-Wechsel, Zeiger und Scrollen, verlorener und wiederhergestellter Kontext und das Aufräumen beim Unmount.

ShaderCanvas#

PropTypStandardWofür
fragmentstring·Der Rumpf des Fragment-Shaders in GLSL ES 1.00. Der Helfer setzt den Uniform-Kopf davor.
colorsstring[][]Bis zu vier CSS-Farben, gelesen im Scope des Canvas. Tokens, color-mix und oklch from funktionieren. Kommen als u_c0 bis u_c3 an.
uniformsRecord<string, number | number[]>·Zusätzliche Uniforms nach Namen, deklariert in deinem Fragment.
speednumber1Tempo von u_time. 1 ist das vorgesehene Tempo.
intensitynumber1Wird als u_intensity weitergegeben, 1 ist die vorgesehene Stärke.
maxDprnumber1.5Höchste Pixeldichte am Desktop.
phoneDprnumber1Höchste Pixeldichte am Telefon.
scalenumber1Renderfaktor für weiche Bilder. 0.5 zeichnet ein Viertel der Pixel.
phoneFpsnumber30Bilder pro Sekunde am Telefon. Der Desktop läuft mit der Rate des Bildschirms.
pointerbooleantrueu_mouse und u_hover folgen einem feinen Zeiger über dem Elternelement.
stillnumber6Sekunden Shader-Zeit für das Standbild bei reduzierter Bewegung und für das erste Bild.
fallbackstring | ReactNode·Ein CSS-Hintergrund oder ein Node. Steht da ohne JavaScript, ohne WebGL, bis zum ersten Bild und nach verlorenem Kontext.
maskstring·Eine CSS-Maske über der ganzen Ebene, etwa ein weicher Rand.

ShaderBackdrop#

PropTypStandardWofür
layerReactNode·Der ShaderCanvas.
scrim'none' | 'soft' | 'center' | 'left' | 'bottom' | 'strong''none'Schatten zwischen Bild und Inhalt, geformt wie der Textblock, den er schützt.
scrimStrengthnumber0.55Wie stark der Scrim deckt, 0 bis 1. Bei strong ist der Standard 0.78.
scrimColorstringvar(--background)Farbe des Scrims. In einer Karte nimm var(--card).
childrenReactNode·Inhalt über dem Bild. Ohne Children rendert nur die Ebene und füllt ihr relatives Elternelement.
className, contentClassNamestring·Klassen für die äußere Box und für die Inhaltsbox.

Jeder Hintergrund des Kits teilt dieselben Props intensity, speed, colors, interactive, fallback und die drei Scrim-Props. Jeder Shader bekommt diese Uniforms von selbst.

UniformTypWofür
u_resvec2Größe des Canvas in Gerätepixeln.
u_timefloatSekunden, gezählt nur, solange der Shader läuft, mal speed.
u_mouse, u_hovervec2, floatZeiger in 0 bis 1 mit y nach oben, geglättet, und wie weit ein feiner Zeiger über dem Elternelement ist.
u_scrollfloat0, wenn die Oberkante den unteren Fensterrand trifft, 1, wenn die Unterkante oben hinausläuft.
u_darkfloat1 innerhalb von .dark, so trägt ein Shader Light und Dark Mode.
u_qualityfloat1 am Desktop, 0.55 am Telefon. Nutze es für weniger Ebenen, Reihen oder Partikel.
u_intensity, u_dprfloatDie Prop intensity und die Gerätepixel je CSS-Pixel.
u_c0 to u_c3vec3Die Farben aus der Prop colors in sRGB 0 bis 1.

Helfer für CSS-3D#

Für die CSS-Flächen und Szenen gibt es depth. Die Helfer schreiben CSS-Variablen direkt an das Element, so bewegt sich die Fläche ohne React-Render.

HelferGibt zurückWofür
usePointerTilt()ref, handlersDas Element neigt sich zu Maus oder Stift. Schreibt --tilt-x, --tilt-y, --glare-x und --glare-y ohne React-Render.
useActive()ref, booleanWahr, solange das Element zu sehen und der Tab sichtbar ist. Leerlaufbewegung läuft nur dann.
SlabComponentEin flacher Körper mit echter Dicke, so zeigt eine gedrehte Karte oder ein Gerät eine Kante.
Glare, tiltTransformComponent, stringEin weiches Licht, das dem Zeiger folgt, und der gemeinsame Ausdruck für die Drehung.

Text auf 3D#

Text auf einem bewegten Bild braucht Ruhe darunter. Der Scrim legt einen Schatten in der Farbe der Seite zwischen Bild und Inhalt und folgt der Form deines Textblocks. Center passt zu einer mittigen Überschrift, left zu einem Hero mit Text links, bottom zu Text am unteren Rand und soft zu Text über der ganzen Fläche. Strong deckt am meisten, für lange Absätze oder Formulare.

  • Prüf den Kontrast am hellsten Moment des Bildes, mindestens 4,5 zu 1 für Fließtext und 3 zu 1 für große Überschriften.
  • In einer Karte gehört der Scrim in die Kartenfarbe, also scrimColor="var(--card)".
  • Am Telefon deckt der Scrim bei center und left die ganze Fläche, weil der Text dort fast die volle Breite einnimmt.
  • Knöpfe und Links auf dem Bild behalten ihre volle Fläche und ihren Fokusring, der Shader nimmt nie Klicks an.

Standbild und reduzierte Bewegung#

Jede 3D-Component hat ein Standbild. Bei den Hintergründen ist es ein CSS-Bild in denselben Farben, es steht ohne JavaScript, ohne WebGL, bis zum ersten echten Bild und nach einem verlorenen Kontext da und blendet weich über. Mit fallback gibst du ein eigenes Standbild vor.

Bei reduzierter Bewegung zeichnet der Shader ein einziges ruhiges Bild und bleibt dann stehen. Die CSS-Flächen ruhen in ihrer fertigen Lage, Szenen zeigen alle Ebenen geordnet und lesbar. Auf Touch-Geräten neigt sich nichts zum Finger, die Fläche bleibt in ihrer Ausgangslage.

Leistung#

  • Die Pixeldichte ist gedeckelt, 1,5 am Desktop und 1 am Telefon. Für weiche Bilder halbiert scale die Auflösung noch einmal.
  • Telefone zeichnen 30 Bilder pro Sekunde mit u_quality 0,55, also weniger Ebenen und Partikel. Der Desktop läuft mit der Rate des Bildschirms.
  • Ein Shader startet erst im sichtbaren Bereich, pausiert außerhalb und im verborgenen Tab und gibt beim Unmount alles frei.
  • CSS-3D bewegt nur transform und opacity. Zeiger und Scrollen laufen über CSS-Variablen, nicht über React-State.
  • Jede Shader-Component ist für unter 8 KB gzip und 60 Bilder pro Sekunde am Desktop gebaut. Setz sie mit client:visible ein, dann lädt ihr Code erst kurz vor dem sichtbaren Bereich.

Ein eigener Shader#

Für einen eigenen Hintergrund schreibst du nur den Fragment-Shader. GLSL_NOISE bringt hash, noise und fbm mit, ACCENT_PALETTE die Akzentfarbe mit zwei Nachbarn und einem hellen Ton. Gib das Ergebnis vormultipliziert aus, dann scheint die Seite durch, und derselbe Shader passt auf Hintergrund, Fläche und Karte.

src/components/Mist.tsx
import { ShaderCanvas, ShaderBackdrop, GLSL_NOISE, ACCENT_PALETTE } from '@/components/ui/webgl';

const FRAG = `${GLSL_NOISE}
void main(){
  vec2 p = gl_FragCoord.xy / u_res;
  float n = fbm(p * 3. + vec2(u_time * .05, 0.));
  vec3 c = mix(u_c0, u_c1, n);
  float a = smoothstep(.2, .9, n) * .6 * u_intensity;
  gl_FragColor = vec4(c * a, a);
}`;

export function Mist({ children }: { children: React.ReactNode }) {
  return (
    <ShaderBackdrop
      scrim="center"
      layer={<ShaderCanvas fragment={FRAG} colors={ACCENT_PALETTE} scale={0.5} fallback="radial-gradient(60% 60% at 50% 40%, color-mix(in oklab, var(--primary) 30%, transparent), transparent)" />}
    >
      {children}
    </ShaderBackdrop>
  );
}

Oft gesucht

Direkt zu

In die Zwischenablage kopiert