Zum Inhalt springen
UI Kit
Accent

Farbverlauf

Ein Verlauf aus der Akzentfarbe fließt durch den Text. Reines CSS, außerhalb des Bildes pausiert, bei reduzierter Bewegung still.

Eine von 5 Components in Textakzente

3 Variants

Variants

Akzent

Der Akzent geht in ein ruhiges Grau über und zurück, in einer gleichmäßigen Schleife.

Spektrum beim Scrollen

Der Akzent und seine beiden Nachbarn im Farbkreis, bewegt von der Scrollposition statt von der Uhr.

Einmal

Ein Durchgang, wenn die Zeile ins Bild kommt, dann ruht sie. Palette Tinte, Textfarbe in den Akzent.

Details

Was Farbverlauf mitbringt, aus Registry und Quellcode gelesen.

Kategorie
Textakzente
Bereich
Elements
Variants
3
Version
1.0.0 · seit 1.0.0
Datei
components/ui/gradient-sweep.tsx
Bringt mit
utilskit
npm-Pakete
Keine
Sprachen
Texte kommen über Props
Studio
Im Code anpassen

Props

Die Schnittstelle GradientSweepProps aus components/ui/gradient-sweep.tsx. Beschreibungen stammen aus den Kommentaren im Code und sind auf Englisch.

  • childrenPflicht
    React.ReactNode

    The text. It stays real text, the gradient is only its fill.

  • asoptional
    Tag

    h1h2h3h4pspandiv

    Element to render. Default span, so it sits inside a heading.

  • paletteoptional
    GradientSweepPalette

    accentspectrumink

    accent: the accent fades into a quiet grey and back (default). spectrum: the accent and its two neighbours on the color wheel; a neutral accent turns into a quiet violet, rose and azure that keeps the text contrast in both modes. ink: the text color and the accent.

  • modeoptional
    GradientSweepMode

    looponce

    loop: flows on while on screen (default). once: one sweep when it scrolls in, then it rests.

  • triggeroptional
    GradientSweepTrigger

    loadviewin-viewscroll

    view (or in-view, default): a loop pauses off screen, a single sweep waits until the text scrolls in. load: a single sweep plays right away. scroll: the sweep follows the scroll position instead of the clock, one pass on the way through the window, back when you scroll up.

  • scrollRangeoptional
    [number, number]

    With trigger scroll: the part of the way through the window that carries the sweep, 0 to 1. Default [0.05, 0.6].

  • durationoptional
    number

    Seconds for one full cycle. Default 6.

Barrierefreiheit

Aus dem Quellcode der Component abgeleitet.

  • Der Server rendert den Startzustand vollständig, JavaScript ergänzt nur die Interaktion.

Bewegung

  • Bewegt sich mit dem Scrollen, der Fortschritt folgt der Position auf der Seite.
  • Bringt eigene Animationen mit, ruhig getaktet und ohne Wackeln.
  • Bei reduzierter Bewegung (prefers-reduced-motion) bleibt sie ruhig.

Quellcode

Die Datei, die aiw add in dein Projekt legt. Sie gehört danach dir.

Datei anzeigen, 156 Zeilen
components/ui/gradient-sweep.tsx
'use client';

import { useEffect, useRef, useState } from 'react';
import { cn, prefersReducedMotion } from '@/lib/kit';

export type GradientSweepPalette = 'accent' | 'spectrum' | 'ink';
export type GradientSweepMode = 'loop' | 'once';
/** in-view is the same as view */
export type GradientSweepTrigger = 'load' | 'view' | 'in-view' | 'scroll';
type Tag = 'h1' | 'h2' | 'h3' | 'h4' | 'p' | 'span' | 'div';

export interface GradientSweepProps {
  /** The text. It stays real text, the gradient is only its fill. */
  children: React.ReactNode;
  /** Element to render. Default span, so it sits inside a heading. */
  as?: Tag;
  /**
   * accent: the accent fades into a quiet grey and back (default).
   * spectrum: the accent and its two neighbours on the color wheel; a
   * neutral accent turns into a quiet violet, rose and azure that keeps the
   * text contrast in both modes. ink: the text color and the accent.
   */
  palette?: GradientSweepPalette;
  /** loop: flows on while on screen (default). once: one sweep when it scrolls in, then it rests. */
  mode?: GradientSweepMode;
  /**
   * view (or in-view, default): a loop pauses off screen, a single sweep waits
   * until the text scrolls in. load: a single sweep plays right away.
   * scroll: the sweep follows the scroll position instead of the clock, one
   * pass on the way through the window, back when you scroll up.
   */
  trigger?: GradientSweepTrigger;
  /** With trigger scroll: the part of the way through the window that carries the sweep, 0 to 1. Default [0.05, 0.6]. */
  scrollRange?: [number, number];
  /** Seconds for one full cycle. Default 6. */
  duration?: number;
  /** Classes for the element, e.g. text-display or text-h1 */
  className?: string;
}

/*
 * The gradient repeats twice inside a background twice as wide as the text,
 * so shifting it by one text width is a seamless loop. Every color is a
 * token or derived from the accent. The spectrum hues need relative color
 * syntax and fall back to the accent palette where it is missing.
 */
const CSS = `
.kitfx-gs{--kitfx-gs-a:var(--primary);--kitfx-gs-b:color-mix(in oklch,var(--primary) 30%,var(--muted-foreground));--kitfx-gs-c:var(--kitfx-gs-a);background-image:linear-gradient(100deg,var(--kitfx-gs-a) 0%,var(--kitfx-gs-b) 12.5%,var(--kitfx-gs-c) 25%,var(--kitfx-gs-b) 37.5%,var(--kitfx-gs-a) 50%,var(--kitfx-gs-b) 62.5%,var(--kitfx-gs-c) 75%,var(--kitfx-gs-b) 87.5%,var(--kitfx-gs-a) 100%);background-size:200% 100%;background-position:0 0;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;padding-block:.08em;margin-block:-.08em;box-decoration-break:clone;-webkit-box-decoration-break:clone}
.kitfx-gs[data-palette=ink]{--kitfx-gs-a:var(--foreground);--kitfx-gs-b:color-mix(in oklch,var(--primary) 70%,var(--foreground));--kitfx-gs-c:color-mix(in oklch,var(--foreground) 55%,var(--background))}
@supports (color:oklch(from red l c h)){
.kitfx-gs[data-palette=spectrum]{--kitfx-gs-n:clamp(0,1 - c / 0.06,1);--kitfx-gs-a:oklch(from var(--primary) calc(l + (clamp(0.44,(l - 0.5) * 100,0.82) - l) * var(--kitfx-gs-n)) max(c,0.12) h);--kitfx-gs-b:oklch(from var(--primary) calc(l + (clamp(0.44,(l - 0.5) * 100,0.82) - l) * var(--kitfx-gs-n)) max(c,0.12) calc(h + 48 + 12 * var(--kitfx-gs-n)));--kitfx-gs-c:oklch(from var(--primary) calc(l + (clamp(0.44,(l - 0.5) * 100,0.82) - l) * var(--kitfx-gs-n)) max(c,0.12) calc(h - 48 - 12 * var(--kitfx-gs-n)))}
}
.kitfx-gs[data-mode=loop]{animation:kitfx-gs-flow var(--kitfx-gs-dur) linear infinite}
.kitfx-gs[data-mode=loop][data-paused]{animation-play-state:paused}
.kitfx-gs[data-mode=once][data-state=play]{animation:kitfx-gs-once var(--kitfx-gs-dur) cubic-bezier(.25,.6,.2,1) both}
.kitfx-gs[data-mode=once][data-state=wait]{background-position:100% 0}
.kitfx-gs[data-mode=scroll]{background-position:calc((1 - var(--kitfx-p,1)) * 100%) 0;transition:background-position .25s linear}
@keyframes kitfx-gs-flow{to{background-position:-200% 0}}
@keyframes kitfx-gs-once{from{background-position:100% 0}}
@media (prefers-reduced-motion:reduce){.kitfx-gs[data-mode]{animation:none;background-position:0 0;transition:none}}`;

/**
 * Scroll link. Writes --kitfx-p (0 to 1) on the element: 0 when it has
 * travelled `start` of its way through the window, 1 at `end` (0 is its top
 * at the bottom edge, 1 its bottom at the top edge). The value is scaled to
 * the part of that way the page can still scroll, so a line at the end of a
 * short page still finishes. Never with reduced motion, then the CSS
 * fallback 1 keeps the final state.
 */
function useScrollLink(ref: { current: HTMLElement | null }, on: boolean, range: readonly [number, number], onChange?: (p: number) => void) {
  const [start, end] = range;
  const listener = useRef(onChange);
  listener.current = onChange;
  useEffect(() => {
    const el = ref.current;
    if (!el || !on || prefersReducedMotion()) return;
    let frame = 0;
    const span = Math.max(0.0001, end - start);
    const at = (top: number, h: number, vh: number) => Math.min(1, Math.max(0, ((vh - top) / (h + vh) - start) / span));
    const update = () => {
      frame = 0;
      const r = el.getBoundingClientRect();
      const vh = window.innerHeight;
      const rest = Math.max(0, document.documentElement.scrollHeight - vh - window.scrollY);
      const most = at(r.top - rest, r.height, vh);
      const p = most < 0.02 ? 1 : Math.min(1, at(r.top, r.height, vh) / most);
      el.style.setProperty('--kitfx-p', p.toFixed(4));
      listener.current?.(p);
    };
    const onScroll = () => {
      if (!frame) frame = requestAnimationFrame(update);
    };
    update();
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    return () => {
      cancelAnimationFrame(frame);
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('resize', onScroll);
      el.style.removeProperty('--kitfx-p');
    };
  }, [ref, on, start, end]);
}

/**
 * A gradient flows through the text, built from the accent color. Pure CSS,
 * it runs without JavaScript; the script pauses the loop off screen and holds
 * a one time sweep until the text scrolls in. Reduced motion shows the
 * gradient still.
 *   <h1 className="text-display">Websites that <GradientSweep>sell</GradientSweep></h1>
 */
export function GradientSweep({ children, as: Tag = 'span', palette = 'accent', mode = 'loop', trigger = 'view', scrollRange = [0.05, 0.6], duration = 6, className }: GradientSweepProps) {
  const ref = useRef<HTMLElement>(null);
  const scroll = trigger === 'scroll';
  useScrollLink(ref, scroll, scrollRange);
  const [paused, setPaused] = useState(false);
  const [state, setState] = useState<'play' | 'wait'>('play');
  useEffect(() => {
    const el = ref.current;
    if (!el || scroll || prefersReducedMotion()) return;
    if (mode === 'once') {
      if (trigger === 'load') return;
      if (el.getBoundingClientRect().top < window.innerHeight * 0.9) return;
      setState('wait');
    }
    const io = new IntersectionObserver(
      ([e]) => {
        const on = !!e?.isIntersecting;
        if (mode === 'loop') setPaused(!on);
        else if (on) {
          setState('play');
          io.disconnect();
        }
      },
      { rootMargin: mode === 'once' ? '0px 0px -10% 0px' : '0px' },
    );
    io.observe(el);
    return () => io.disconnect();
  }, [mode, scroll, trigger]);
  return (
    <>
      <style dangerouslySetInnerHTML={{ __html: CSS }} />
      <Tag
        ref={ref as never}
        className={cn('kitfx-gs', className)}
        data-palette={palette}
        data-mode={scroll ? 'scroll' : mode}
        data-state={state}
        data-paused={paused ? '' : undefined}
        style={{ '--kitfx-gs-dur': `${duration}s` } as React.CSSProperties}
      >
        {children}
      </Tag>
    </>
  );
}

Häufige Fragen

Wie installiere ich Farbverlauf?

Mit aiw add @ai-website/gradient-sweep. Der Befehl legt die Datei components/ui/gradient-sweep.tsx in dein Projekt, der Code gehört danach dir. Weitere npm-Pakete braucht sie nicht.

Welche Varianten hat Farbverlauf?

3 Varianten, Akzent, Spektrum beim Scrollen und Einmal.

Kann ein KI-Assistent Farbverlauf einbauen?

Ja. Über den MCP-Server der aiw CLI finden Claude, Cursor, GitHub Copilot, Windsurf, Codex und Gemini Farbverlauf mit aiw_components und installieren sie mit aiw_add @ai-website/gradient-sweep.

Oft gesucht

Direkt zu

In die Zwischenablage kopiert