Schimmertext
Ein Licht zieht über den Text. Reines CSS auf der geerbten Farbe, außerhalb des Bildes pausiert, bei reduzierter Bewegung ruhig.
Eine von 5 Components in Textakzente
Details
Was Schimmertext mitbringt, aus Registry und Quellcode gelesen.
- Kategorie
- Textakzente
- Bereich
- Elements
- Variants
- 3
- Version
- 1.0.0 · seit 1.0.0
- Datei
- components/ui/shimmer-text.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 ShimmerTextProps aus components/ui/shimmer-text.tsx. Beschreibungen stammen aus den Kommentaren im Code und sind auf Englisch.
childrenPflichtReact.ReactNodeThe text. It stays real text, the light is only a background.
asoptionalTagh1h2h3h4pspandiv
Element to render. Default span.
triggeroptionalShimmerTriggerloadviewin-viewhoverscroll
view: the light passes again and again while the text is on screen (default). load: the same, also off screen. hover: one pass on every hover and focus of the text or of the link, button or .group around it; the text rests in full color. scroll: one pass of light that follows the scroll position, the text rests in full color.
scrollRangeoptional[number, number]With trigger scroll: the part of the way through the window that carries the light, 0 to 1. Default [0.1, 0.6].
toneoptional'bright' | 'accent'bright: the light is the text color itself on a dimmed base (default). accent: the light takes the accent color.
durationoptionalnumberSeconds for one pass including the rest before the next. Default 2.4.
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, 127 Zeilen
'use client';
import { useEffect, useRef, useState } from 'react';
import { cn, prefersReducedMotion } from '@/lib/kit';
/** in-view is the same as view */
export type ShimmerTrigger = 'load' | 'view' | 'in-view' | 'hover' | 'scroll';
type Tag = 'h1' | 'h2' | 'h3' | 'h4' | 'p' | 'span' | 'div';
export interface ShimmerTextProps {
/** The text. It stays real text, the light is only a background. */
children: React.ReactNode;
/** Element to render. Default span. */
as?: Tag;
/**
* view: the light passes again and again while the text is on screen (default).
* load: the same, also off screen. hover: one pass on every hover and focus
* of the text or of the link, button or .group around it; the text rests in full color.
* scroll: one pass of light that follows the scroll position, the text rests in full color.
*/
trigger?: ShimmerTrigger;
/** With trigger scroll: the part of the way through the window that carries the light, 0 to 1. Default [0.1, 0.6]. */
scrollRange?: [number, number];
/** bright: the light is the text color itself on a dimmed base (default). accent: the light takes the accent color. */
tone?: 'bright' | 'accent';
/** Seconds for one pass including the rest before the next. Default 2.4. */
duration?: number;
/** Classes for the element, e.g. text-h2 or text-sm text-muted-foreground */
className?: string;
}
/*
* The text keeps its inherited color property, only the glyph fill turns
* transparent. So currentColor in the gradient is the inherited color,
* and the effect works on any text color without knowing it.
*/
const CSS = `
.kitfx-sh{--kitfx-sh-band:currentColor;--kitfx-sh-base:color-mix(in oklch,currentColor 52%,transparent);background-image:linear-gradient(100deg,var(--kitfx-sh-base) 0 38%,var(--kitfx-sh-band) 50%,var(--kitfx-sh-base) 62% 100%);background-size:250% 100%;background-repeat:no-repeat;background-position:100% 0;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;animation:kitfx-sh var(--kitfx-sh-dur) linear infinite}
.kitfx-sh[data-tone=accent]{--kitfx-sh-band:var(--primary);--kitfx-sh-base:color-mix(in oklch,currentColor 58%,transparent)}
.kitfx-sh[data-paused]{animation-play-state:paused}
.kitfx-sh[data-trigger=hover]{--kitfx-sh-base:currentColor;--kitfx-sh-band:color-mix(in oklch,currentColor 30%,var(--background));animation:none}
.kitfx-sh[data-trigger=hover][data-tone=accent]{--kitfx-sh-band:var(--primary)}
.kitfx-sh[data-trigger=hover]:is(:hover,:focus-visible,:focus-within),:is(a,button,.group):is(:hover,:focus-visible) .kitfx-sh[data-trigger=hover]{animation:kitfx-sh-once calc(var(--kitfx-sh-dur) * .5) cubic-bezier(.4,0,.2,1) both}
.kitfx-sh[data-trigger=scroll]{--kitfx-sh-base:currentColor;--kitfx-sh-band:color-mix(in oklch,currentColor 30%,var(--background));animation:none;background-position:calc((1 - var(--kitfx-p,1)) * 100%) 0;transition:background-position .25s linear}
.kitfx-sh[data-trigger=scroll][data-tone=accent]{--kitfx-sh-band:var(--primary)}
@keyframes kitfx-sh{0%{background-position:100% 0}65%,100%{background-position:0 0}}
@keyframes kitfx-sh-once{from{background-position:100% 0}to{background-position:0 0}}
@media (prefers-reduced-motion:reduce){.kitfx-sh,.kitfx-sh[data-trigger=hover]:is(:hover,:focus-visible,:focus-within),:is(a,button,.group):is(:hover,:focus-visible) .kitfx-sh[data-trigger=hover]{animation:none;background:none;-webkit-text-fill-color:currentColor}}`;
/**
* 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 light passes over the text, like a status line that is working or a
* badge that wants a second look. Pure CSS, it runs without JavaScript; the
* script only pauses it while it is off screen. With reduced motion the
* text stands still in its full color.
* <ShimmerText className="text-sm">Generating your site</ShimmerText>
*/
export function ShimmerText({ children, as: Tag = 'span', trigger = 'view', scrollRange = [0.1, 0.6], tone = 'bright', duration = 2.4, className }: ShimmerTextProps) {
const ref = useRef<HTMLElement>(null);
const mode = trigger === 'in-view' ? 'view' : trigger;
useScrollLink(ref, mode === 'scroll', scrollRange);
const [paused, setPaused] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el || mode !== 'view' || prefersReducedMotion()) return;
const io = new IntersectionObserver(([e]) => setPaused(!e?.isIntersecting));
io.observe(el);
return () => io.disconnect();
}, [mode]);
return (
<>
<style dangerouslySetInnerHTML={{ __html: CSS }} />
<Tag
ref={ref as never}
className={cn('kitfx-sh', className)}
data-trigger={mode}
data-tone={tone}
data-paused={paused ? '' : undefined}
style={{ '--kitfx-sh-dur': `${duration}s` } as React.CSSProperties}
>
{children}
</Tag>
</>
);
}Häufige Fragen
Wie installiere ich Schimmertext?
Mit aiw add @ai-website/shimmer-text. Der Befehl legt die Datei components/ui/shimmer-text.tsx in dein Projekt, der Code gehört danach dir. Weitere npm-Pakete braucht sie nicht.
Welche Varianten hat Schimmertext?
3 Varianten, Statuszeile, Überschrift beim Scrollen und Bei Hover.
Kann ein KI-Assistent Schimmertext einbauen?
Ja. Über den MCP-Server der aiw CLI finden Claude, Cursor, GitHub Copilot, Windsurf, Codex und Gemini Schimmertext mit aiw_components und installieren sie mit aiw_add @ai-website/shimmer-text.