Communitygithub.com

anhlamtruong/living-ui-effects

Füge einer Webseite nach dem Laden interaktive Echtzeiteffekte hinzu: WebGL-Aurora-Shader, holografische 3D-Kippkarten, Raster-Spotlight, RGB-Split-Glitch-Überschriften und Hover-Text-Scramble.

Was ist living-ui-effects?

Diese Skill-Sammlung fügt einer Webseite vier visuelle Effekte hinzu, die nach dem Laden des Inhalts für Bewegung und Interaktivität sorgen: einen WebGL-Aurora-Shader im Hero-Bereich, der auf die Mauszeigerbewegung reagiert, holografische Karten mit 3D-Kippeffekt und Glanzlicht, ein Raster-Spotlight, das dem Cursor folgt, sowie RGB-Split-Glitch-Überschriften und Hover-Text-Scramble für Links und Buttons. Die Effekte sind als React/Next.js-Komponenten (z. B. `FxRoot`, `HeroShader`, `GlitchHeading`, `ScrambleText`) implementiert, die einfach in eine bestehende App integriert werden können. Der Code nutzt eine gemeinsame `requestAnimationFrame`-Schleife, die sich bei Inaktivität schlafen legt, und vermeidet Layout-Shift, indem nur `transform`, `opacity` und `clip-path` animiert werden. Besonderes Augenmerk liegt auf Barrierefreiheit (z. B. `aria-hidden`-Kopien für Glitch-Effekte) und Konsistenz bei verschiedenen Eingabegeräten (Maus vs. Touch). Die Effekte sind über CSS-Variablen und Props konfigurierbar und eignen sich für Portfolio-Seiten, Produktdemos oder jede Website, die einen „lebendigen“ Eindruck vermitteln soll.

Funktioniert mit~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/anhlamtruong/living-ui-skills/tree/HEAD/skills/living-ui-effects

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Live-Effekte

Vier Effekte, die eine Seite in Bewegung halten, sobald der Inhalt auf dem Bildschirm ist:

  • Aurora-Hero: Ein WebGL-Feld aus weichem Licht hinter dem Hero, das sich um den Mauszeiger biegt.
  • Hologramm-Karten: Eine Karte neigt sich zum Cursor hin, hebt sich an und zeigt einen Glanz sowie einen schillernden Rand.
  • Raster-Spotlight: Das Hintergrundraster der Seite leuchtet in einem weichen Kreis um den Cursor auf.
  • Glitch- und Scramble-Text: Überschriften werden beim Eintreffen per RGB-Split verzerrt, und Link-Labels entschlüsseln sich beim Darüberfahren.

Der Maßstab ist „lebendig, niemals im Weg“. Die Nutzer sollen spüren, dass die Seite auf sie reagiert, aber nichts darf ruckeln, das Layout verschieben, Klicks stehlen oder Text vor einem Screen-Reader verstecken.

Live-Beispiel: https://www.lamanhtruong.com (über die Projektkarten fahren, über den Hero bewegen, über die Navigation fahren).

Was sich in assets/fx/ befindet

DateiRolle
ticker.tsEine gemeinsame requestAnimationFrame-Schleife, die schläft, wenn niemand Frames anfordert
built.tsBereitschaftstor (isBuilt, buildActive), frozen(), deterministischer PRNG, Pointer-Media-Query
aurora.tsDer WebGL-Shader, sein Qualitätsskalierer und Lebenszyklus (createAurora)
hero-shader.tsx<HeroShader />, der React-Wrapper für die Aurora
holo.tsNeigung + Glanz + Schimmer auf Karten, Federphysik, eingefügte Überlagerung
spotlight.tsRaster-Spotlight, das dem Cursor folgt (nur transform)
glitch.ts, glitch-copies.tsxGlitch-Planer, <GlitchCopies /> und <GlitchHeading />
scramble.ts, scramble-text.tsxScramble-Ausführung und der <ScrambleText />-Markup-Hook
fx-root.tsx<FxRoot />: bindet Holo, Spotlight, Glitch und Scramble einmal ein
fx.cssAlle Stile, thematisiert mit --fx-*-Custom-Properties

Der Code benötigt nur React; keine Animationsbibliothek. Die Vorlagen sind typsicher unter striktem TypeScript gegen die React 18 und 19 Typen.

Installation

  1. Kopiere assets/fx/ in die App, z. B. src/components/fx/. Behalte die Dateien zusammen, da sie sich gegenseitig über relative Pfade importieren.
  2. Binde es ein. Die Seite selbst kann eine statische Serverkomponente bleiben; nur FxRoot und HeroShader sind Client-Komponenten.
import { FxRoot } from "@/components/fx/fx-root";
import { HeroShader } from "@/components/fx/hero-shader";
import { GlitchHeading } from "@/components/fx/glitch-copies";
import { ScrambleText } from "@/components/fx/scramble-text";

export default function Page() {
  return (
    <div className="fx-grid">                        {/* festes Basisraster; überspringe, wenn du dein eigenes hast */}
      <main className="relative z-[1]">              {/* Inhalt liegt über Raster + Spotlight */}
        <section className="relative min-h-[80vh]">  {/* Hero muss position: relative sein */}
          <HeroShader />                             {/* erstes Kind, hinter dem Hero-Inhalt */}
          <h1>…</h1>
        </section>

        <GlitchHeading as="h2" className="text-5xl font-semibold">Ausgewählte Arbeiten</GlitchHeading>

        <article data-holo className="rounded-2xl border p-6">
          <a href="/work/x" className="inline-flex items-center gap-2">
            <ScrambleText>Zur Fallstudie</ScrambleText> <ArrowIcon />
          </a>
        </article>
      </main>
      <FxRoot />                                     {/* einmal pro Seite, innerhalb des Raster-Wrappers */}
    </div>
  );
}
  1. Gestalte es: Bearbeite die --fx-*-Variablen am Anfang von fx.css und übergib palette / intensity an <HeroShader /> (siehe „Theming“ unten).
  2. Überprüfe mit den Playwright-Checks in references/verification.md, bevor du es als erledigt betrachtest.

Schalte Teile mit Props aus: <FxRoot holo={false} spotlight={false} />. Richte die Neigung auf vorhandene Karten aus, ohne ihr Markup zu berühren: <FxRoot holo={{ selector: ".card", within: "main" }} />.

Regeln, die für eine flüssige Darstellung sorgen

Diese Regeln unterscheiden „Premium“ von „Ruckeln“. Jede existiert, weil ihr Verstoß ein sichtbares Problem verursacht hat.

  1. Warte, bis der Inhalt bereit ist. Neige, verzerre oder scrambeln niemals etwas, während ein anderes Skript es noch enthüllt oder vermisst. Eine Transformation an einem Element während der Enthüllung korrumpiert die Messungen dessen, der es animiert, und das Ergebnis sieht aus wie ein Stottern. built.ts implementiert einen Vertrag: Solange html[data-build="on"] gesetzt ist, zählt ein Element erst, wenn sein [data-build-item] und [data-build-section] [data-built] tragen. Ohne Build-Engine auf der Seite gilt alles sofort als erstellt. Jede Intro-Animation kann sich durch Setzen dieser Attribute anmelden. Details: references/gating-and-performance.md.

  2. Verwende eine Frame-Schleife und lass sie schlafen. Jeder Effekt abonniert ticker.ts, anstatt selbst requestAnimationFrame aufzurufen. Ein Abonnent gibt false zurück, wenn er sich beruhigt hat, und wenn niemand Frames benötigt, stoppt die Schleife. Eine Leerlaufseite kostet dann nichts, und die Effekte können sich nicht um Frames streiten. Glitch verwendet überhaupt keine Frames: Es sind CSS-Keyframes, die durch ein Attribut umgeschaltet werden.

  3. Animiere nur transform, opacity und clip-path. Diese bleiben auf dem Compositor. Nichts hier ändert die Größe, Position im Fluss oder Schriftmetriken, sodass Layout-Shift von Haus aus gleich Null ist. Der Scramble behält den echten Text an Ort und Stelle (transparent) und zeichnet die Glyphen in einer Überlagerung, sodass selbst ein sich änderndes Label niemals die Breite ändert.

  4. Passe den Effekt an die Eingabe an. Neigung und das Folgespotlight existieren nur auf (hover: hover) and (pointer: fine). Auf Touch wird das Neigen unter einem Finger als Fehler gelesen, daher erhält Touch eine kurze Einblendung am Berührungspunkt und eine selbsttreibende Shader-Linse stattdessen.

  5. Lasse Reacts DOM im Ruhezustand in Ruhe. Der Holo-Überlagerungs-<span> und das Inline-transform werden hinzugefügt, wenn eine Karte aktiviert wird, und entfernt, wenn sie wieder in den Ruhezustand zurückgekehrt ist. Im Ruhezustand ist das DOM genau das, was React gerendert hat, sodass Wiederholungen, Hydration und die eigenen :hover-Stile der Karte weiterhin funktionieren. Glitch-Ebenen und Scramble-Spannen werden im Markup geschrieben (<GlitchCopies>, <ScrambleText>) und nicht zur Laufzeit um React-eigene Knoten herum eingefügt; die Umstrukturierung von Reacts Baum von außen unterbricht die Rekonsiliation.

  6. Halte Text real. Glitch-Kopien sind nur Client-seitig und aria-hidden, sodass das statische HTML, die Suche auf der Seite und Screen-Reader jede Überschrift einmal sehen. Die Scramble-Überlagerung ist aria-hidden und das echte Label bleibt während des gesamten Vorgangs der zugängliche Name.

  7. Räume auf und überlebe StrictMode. Jedes setup* gibt eine Dispose-Funktion zurück, die Listener, Observer, Überlagerungen und Inline-Stile entfernt. Der Shader gibt seinen Puffer und sein Programm frei, ruft aber bewusst nicht WEBGL_lose_context auf, da React StrictMode auf derselben Canvas einhängt, aushängt und wieder einhängt, und ein verlorener Kontext bleibt verloren.

  8. Budgetiere die GPU. Der Shader rendert mit reduzierter Auflösung unter einem Pixelbudget, zeichnet nur, während er sichtbar ist, begrenzt Zeichnungen auf ~80 pro Sekunde auf hochfrequenten Bildschirmen und senkt die Qualität einmal, wenn die Seite mit unter ~38 fps läuft. Er weigert sich, auf einem Software-Fallback zu starten (failIfMajorPerformanceCaveat) und rendert nichts, anstatt etwas Langsames zu rendern.

Die Effekte

Aurora-Hero-Shader (aurora.ts, hero-shader.tsx)

Der Shader ist domain-verzerrtes Wertrauschen (fbm warped by fbm), gezeichnet als breites Leuchten plus dünne helle Stränge entlang der verzerrten Konturen. Der Zeiger wirkt wie eine Linse: Er vergrößert und verwirbelt die Strömung in der Nähe. Beim Betreten des Hero, beim Klicken oder bei schnellen Bewegungen fällt ein weicher Wellenring ab. Die Ausgabe ist premultiplied alpha und an jeder Kante ausgeblendet, sodass der Seitenhintergrund und ein eventuelles festes Raster durchscheinen und der Hero in den nächsten Abschnitt übergeht, anstatt in einer Box zu enden.

Lebenszyklus: Die Canvas blende über 1,8 s ein, sobald ihr Abschnitt erstellt ist. Sie rendert nur, während der Abschnitt auf dem Bildschirm ist (IntersectionObserver mit einem 80 px Rand) und der Tab sichtbar ist. Wenn WebGL fehlt, rendert sie nichts.

Knöpfe:

  • palette: { veil, primary, secondary, accent } als Hex. Die Palette wird in den Shader kompiliert, daher erfordern Änderungen ein erneutes Mounten.
  • intensity: multipliziert das Licht. 0,6 ist ruhig, 1,4 ist laut.
  • Textlesbarkeit: Der Shader neigt das Licht weg von der unteren linken Ecke, wo sich normalerweise der Hero-Text befindet. Wenn dein Text woanders liegt, ändere die Zeile bias (siehe Referenz).

Lies references/aurora-shader.md, bevor du die Shader-Mathematik, die Geschwindigkeit oder den Qualitätsskalierer änderst.

Hologramm-Karten (holo.ts)

Delegiertes pointerover findet die Karte unter einem feinen Zeiger. Beim Aktivieren misst es die unveränderte Box der Karte, injiziert einen aria-hidden-Überlagerungs-<span> und führt eine kritisch gedämpfte Feder (sub-step bei 120 Hz, damit sie sich bei 60 oder 144 fps gleich verhält) in Richtung einer Neigung aus, die auf den Zeiger zielt. Der Rand unter dem Cursor kommt zum Betrachter. Beim Verlassen federt es mit einem weichen Überschwingen zurück und entfernt alles, sobald es sich beruhigt hat.

  • Die Neigung skaliert mit der Kartengröße, 2,5° bis 7,5°: Große Karten neigen sich weniger, damit ihre entfernten Kanten nicht schwingen. Perspektive ist das 1,6-fache der größeren Seite. Die Anhebung beträgt 3 px.
  • Die Überlagerung hat ein radiales Glanzlicht am Zeiger, eine schwache diagonale Farbwäsche und einen konischen Gradientenring, der auf den Rand maskiert ist (überall gedimmt, in der Nähe des Zeigers hell).
  • Bei einer overflow: hidden-Karte wird die 1 px große Border-Box abgeschnitten, daher wird der Ring knapp innerhalb gezeichnet (.fx-holo--in, automatisch erkannt).
  • Die Überlagerung sitzt bei z-index: 3 mit pointer-events: none, sodass sie über Steuerelemente in der Karte (einen Video-Umschalter, einen Link) malt, ohne Klicks zu blockieren.
  • Eine statisch positionierte Karte erhält position: relative nur, während sie aktiv ist.
  • Opt-out: data-holo="off" auf einer Karte oder einem beliebigen Vorfahren. Pausiere alles Neigen mit html[data-fx-freeze], während jemand echte Rechtecke liest (ein Inspektor, ein Screenshot-Durchlauf). Der living-ui-swarm-Inspect-Umschalter wird automatisch erkannt.

Raster-Spotlight (spotlight.ts, div.fx-spot)

Ein festes 560 px großes Fenster, radial maskiert, enthält eine hellere Kopie des Basisrasters. Jeder Frame bewegt nur zwei Transformationen: Das Fenster folgt dem Cursor, und das Raster im Inneren wird um floor(x / cell) * cell - x zurückverschoben, sodass seine Linien genau auf dem Gitter des Basisrasters landen. Beide Raster müssen am Viewport-Ursprung verankert sein, und .fx-grid::before ist es. Der nachlaufende Lerp beruhigt sich und die Schleife schläft, wenn der Cursor anhält. Es versteckt sich bei Fensterunschärfe oder wenn der Zeiger das Fenster verlässt. Touch erhält eine 360 ms Einblendung am Berührungspunkt.

Rasterzelle, Abstand der feinen Linien und Fenstergröße stammen von --fx-grid-cell, --fx-grid-fine und --fx-spot-size (px-Werte), sodass CSS die einzige Quelle der Wahrheit bleibt.

Glitch-Überschriften (glitch.ts, <GlitchHeading> / <GlitchCopies>)

Markup: Ein .fx-glitch[data-fx-glitch]-Wrapper, der die echte Überschrift (.fx-glitch__base) und zwei reine Client-Kopien enthält. GlitchHeading schreibt alles; verwende GlitchCopies direkt, wenn die Überschrift bereits existiert und andere Attribute trägt.

  • Eingang: 560 ms, wenn eine erstellte Überschrift zu mindestens 50 % in Sicht ist. Dieselbe Überschrift wird innerhalb von 2,5 s nicht wiederholt.
  • Mikro-Glitch: 260 ms auf einer zufälligen sichtbaren Überschrift alle 8–14 s.
  • Die Zufälligkeit ist gesät, sodass der Rhythmus bei jedem Besuch und in Tests gleich ist.
  • Es glitcht immer nur eine Überschrift gleichzeitig; Eingänge reihen sich hintereinander.
  • Implementiert als reine CSS-Keyframes, die durch [data-glitch] umgeschaltet werden: Ausschnitt mit clip-path: inset(), Versatz mit translate3d und Farbaufteilung mit text-shadow.
  • --fx-gi skaliert die Versätze pro Durchlauf.

Scramble-Text (scramble.ts, <ScrambleText>)

Umschließe nur das Label, niemals das Icon: <a><Icon/> <ScrambleText>{label}</ScrambleText></a>.

  • Auslöser: Hover (nicht Touch) oder Tastatur-:focus-visible auf dem umschließenden a, button oder [data-fx-scr-host].
  • Dauer: 360–520 ms, abhängig von der Länge.
  • Auflösung: Zeichen lösen von links nach rechts mit einem kleinen Zittern auf. Der ungelöste Rest wird mit ~18 Hz neu gezeichnet, nicht jeden Frame, sodass es wie Dekodierung und nicht wie Rauschen wirkt.
  • Glyphen-Pools: groß-/kleinschreibungsabhängig, und Satzzeichen wie @ . / : - bleiben erhalten, sodass E-Mails und Pfade während des Scrambles lesbar bleiben.
  • Bereinigung: Die Überlagerung wird immer geleert, einschließlich eines Timer-Fallbacks für Hintergrund-Tabs, bei denen rAF stoppt.

Verwende inline für einfache Inline-Links (behält die Unterstreichung). Verwende quiet bei ausgefüllten Buttons, zusammen mit --fx-scr-tail: currentColor auf dem Button.

Theming

VariableVerwendet vonStandard
--fx-accentGlitch-Ebene A, Scramble-Glyphen, Spotlight-Leuchten, Holo-Schatten#5eead4
--fx-hotGlitch-Ebene B#f472b6
--fx-sheen-1..5Holo-Ring, in der Reihenfolge um den KreisTeal, Blau, Violett, Pink, Bernstein
--fx-grid-line, --fx-grid-fine-lineBasisraster-Linienniedriges Alpha-Schiefer
--fx-grid-cell, --fx-grid-fineRasterabstand (px)96px, 24px
--fx-spot-sizeSpotlight-Fenster (px)560px
--fx-monoScramble-Glyphen-SchriftartSystem-Mono
--fx-scr-tailGlyphenfarben-Override pro Elementnicht gesetzt

Farben mischen sich über color-mix(in srgb, …), das in allen aktuellen Browsern unterstützt wird. Der Shader nimmt seine eigene Palette: <HeroShader palette={{ accent: "#22d3ee", secondary: "#f0abfc" }} intensity={0.8} />.

Wähle Shader-Farben aus derselben Familie wie die Seitenakzentfarbe. Der Shader ist eine Stimmung, keine zweite Markenfarbe; wenn ein Erstbesucher den Shader vor der Überschrift bemerkt, ist er zu laut.

Schichtung

Schichtz-IndexHinweise
Basisraster (.fx-grid::before, fixed)0Am Viewport-Ursprung verankert
Spotlight (div.fx-spot, fixed)0Nach dem Raster in der Baumreihenfolge, malt also darüber
Inhalt (main, footer)1Muss mit z-index: 1 positioniert sein
Hero-Shader (canvas.fx-shader)autoAbsolut innerhalb des Hero, erstes Kind, späterer Inhalt malt darüber
Holo-Überlagerung3 innerhalb ihrer Kartepointer-events: none
Fixer Header, etwaige Build-Animation-Überlagerungs-Canvasüber dem InhaltNicht betroffen

Wenn das Spotlight über deinem Text erscheint, fehlt dem Inhalts-Wrapper position: relative; z-index: 1.

Bewegungspräferenzen

Auf lamanhtruong.com hat sich der Eigentümer dafür entschieden, jeden Effekt für alle abzuspielen, einschließlich Besucher mit prefers-reduced-motion, da die Bewegung der Kern des Portfolios ist. Das ist eine legitime Produktentscheidung, aber treffe sie bewusst. Um die Einstellung stattdessen zu respektieren:

  • <FxRoot motion="reduce">: Überschreibt den CSS-Wert, sodass die Effekte auf allen Geräten deaktiviert werden. Setzt media auf (prefers-reduced-motion: no-preference).
  • <FxRoot motion="allow"> (Standard): Zeigt Effekte unabhängig von der Browsereinstellung an.

Referenzen

DateiEnthält
references/verification.mdPlaywright-Checks: Holo-Neigung, Spotlight-Follow, Glitch-Zeitsteuerung, Scramble-Auflösung, Accessibility-Prüfung, GPU-Budget
references/gating-and-performance.mdBereitschaftstor-Details: Was macht built.ts, wie verzögern Intro-Animationen die Effekte, wie testet man mit einem Build-Engine-Emulator
references/aurora-shader.mdShader-Interna: Die Rauschfunktion, die Linsenverzerrung, der Qualitätsskalierer, wie man das bias ändert, um den Text an anderen Stellen zu lesen, wie man den Shader in einer Nicht-React-Umgebung ausführt

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

Verwandte Skills