Zum Inhalt springen
UI Kit
Accent

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

3 Variants

Variants

Statuszeile

Der Schritt, der gerade läuft, schimmert, die anderen ruhen.

Überschrift beim Scrollen

Ein Durchgang Akzentlicht über einer großen Zeile, getragen von der Scrollposition. In Ruhe steht die Zeile in voller Farbe.

Bei Hover

Textlinks ruhen in voller Farbe, ein Lichtdurchgang bei Hover und Tastaturfokus.

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

Props

Die Schnittstelle ShimmerTextProps aus components/ui/shimmer-text.tsx. Beschreibungen stammen aus den Kommentaren im Code und sind auf Englisch.

  • childrenPflicht
    React.ReactNode

    The text. It stays real text, the light is only a background.

  • asoptional
    Tag

    h1h2h3h4pspandiv

    Element to render. Default span.

  • triggeroptional
    ShimmerTrigger

    loadviewin-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.

  • durationoptional
    number

    Seconds 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
components/ui/shimmer-text.tsx
'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.

Oft gesucht

Direkt zu

In die Zwischenablage kopiert