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
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
- Mit KI bauen
- Per MCP in Claude, Cursor, Copilot und Codex
Props
Die Schnittstelle GradientSweepProps aus components/ui/gradient-sweep.tsx. Beschreibungen stammen aus den Kommentaren im Code und sind auf Englisch.
childrenPflichtReact.ReactNodeThe text. It stays real text, the gradient is only its fill.
asoptionalTagh1h2h3h4pspandiv
Element to render. Default span, so it sits inside a heading.
paletteoptionalGradientSweepPaletteaccentspectrumink
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.
modeoptionalGradientSweepModelooponce
loop: flows on while on screen (default). once: one sweep when it scrolls in, then it rests.
triggeroptionalGradientSweepTriggerloadviewin-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].
durationoptionalnumberSeconds 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
'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.