Zum Inhalt springen
UI Kit
Accent

Raster mit Verlauf

Ein Linien- oder Kachelraster, das ausblendet, mit Feldern, die in der Akzentfarbe aufleuchten.

Eine von 6 Components in Backgrounds

3 Variants

Variants

Linien mit Puls

Haarlinien durch die Mitte setzen sich beim Hochscrollen an ihren Platz, einige Felder pulsieren langsam.

Kacheln

Gerundete Kacheln mit kleinen Fugen, hervorgehobene in der Akzentfarbe.

Gestrichelt von oben

Ein leises gestricheltes Raster wie Zeichenpapier, das nach unten ausläuft.

Details

Was Raster mit Verlauf mitbringt, aus Registry und Quellcode gelesen.

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

Props

Die Schnittstelle GridFadeProps aus components/ui/grid-fade.tsx. Beschreibungen stammen aus den Kommentaren im Code und sind auf Englisch.

  • variantoptional
    GridFadeVariant

    linessquaresdashed

    lines: hairline grid. squares: rounded tiles with gaps. dashed: a quiet dashed grid.

  • celloptional
    number

    Cell size in px, default 48

  • fadeoptional
    GridFadeFade

    radialtopbottomnone

    Where the grid stays visible: radial around the middle, toward the top or the bottom

  • highlightsoptional
    number

    How many cells light up in the accent color, default 0

  • pulseoptional
    boolean

    Let the highlighted cells pulse slowly, default true

  • seedoptional
    number

    Same seed, same cells

  • scrollRevealoptional
    boolean

    The grid settles from a little larger and fainter into place while the stage scrolls up to the middle of the window. Default false.

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, 152 Zeilen
components/ui/grid-fade.tsx
'use client';

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

export type GridFadeVariant = 'lines' | 'squares' | 'dashed';
export type GridFadeFade = 'radial' | 'top' | 'bottom' | 'none';

export interface GridFadeProps {
  /** lines: hairline grid. squares: rounded tiles with gaps. dashed: a quiet dashed grid. */
  variant?: GridFadeVariant;
  /** Cell size in px, default 48 */
  cell?: number;
  /** Where the grid stays visible: radial around the middle, toward the top or the bottom */
  fade?: GridFadeFade;
  /** How many cells light up in the accent color, default 0 */
  highlights?: number;
  /** Let the highlighted cells pulse slowly, default true */
  pulse?: boolean;
  /** Same seed, same cells */
  seed?: number;
  /** The grid settles from a little larger and fainter into place while the stage scrolls up to the middle of the window. Default false. */
  scrollReveal?: boolean;
  className?: string;
}

const CSS = `
@keyframes kitfx-grid-pulse { 0%, 100% { opacity: 0.3; } 50% { opacity: 0.9; } }
.kitfx-grid * { animation-play-state: var(--kitfx-play, running); }
.kitfx-grid[data-scroll] > svg { --e: min(1, var(--kitfx-p, 0.5) * 2); opacity: calc(0.3 + 0.7 * var(--e)); transform: scale(calc(1.06 - 0.06 * var(--e))); transform-origin: 50% 45%; transition: opacity .3s linear, transform .3s linear; }
@media (prefers-reduced-motion: reduce) { .kitfx-grid * { animation: none !important; } .kitfx-grid[data-scroll] > svg { opacity: 1; transform: none; } }
`;

const FADE: Record<GridFadeFade, string | undefined> = {
  radial: 'radial-gradient(ellipse 70% 65% at 50% 45%, #000 25%, transparent 78%)',
  top: 'linear-gradient(to bottom, #000 10%, transparent 75%)',
  bottom: 'linear-gradient(to top, #000 10%, transparent 75%)',
  none: undefined,
};

function seeded(seed: number) {
  let a = seed >>> 0 || 1;
  return () => {
    a = (a + 0x6d2b79f5) | 0;
    let t = Math.imul(a ^ (a >>> 15), 1 | a);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

/**
 * Scroll pass. While `on`, writes --kitfx-p on the element: 0 when its top
 * meets the bottom of the window, 1 when its bottom leaves at the top. Never
 * with reduced motion, the CSS then falls back to the resting value.
 */
function useScrollPass(ref: { current: HTMLElement | null }, on: boolean) {
  useEffect(() => {
    const el = ref.current;
    if (!el || !on || prefersReducedMotion()) return;
    let frame = 0;
    const update = () => {
      frame = 0;
      const r = el.getBoundingClientRect();
      const p = Math.min(1, Math.max(0, (window.innerHeight - r.top) / (r.height + window.innerHeight)));
      el.style.setProperty('--kitfx-p', p.toFixed(4));
    };
    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]);
}

/**
 * A line or tile grid that fades out, with optional cells that light up in
 * the accent color and pulse. Lines run through the middle of the parent, so
 * the grid sits symmetric at every width. Fills its parent, decorative,
 * static with reduced motion.
 */
export function GridFade({ variant = 'lines', cell = 48, fade = 'radial', highlights = 0, pulse = true, seed = 11, scrollReveal = false, className }: GridFadeProps) {
  const ref = useRef<HTMLDivElement>(null);
  useScrollPass(ref, scrollReveal);
  const id = `kitfx-grid-${useId().replace(/[^a-zA-Z0-9_-]/g, '')}`;
  useEffect(() => {
    const el = ref.current;
    if (!el || !pulse || !highlights || typeof IntersectionObserver === 'undefined') return;
    const io = new IntersectionObserver(([e]) => el.style.setProperty('--kitfx-play', e.isIntersecting ? 'running' : 'paused'));
    io.observe(el);
    return () => io.disconnect();
  }, [pulse, highlights]);

  // Cells around the middle, never twice the same
  const rnd = seeded(seed);
  const used = new Set<string>();
  const cells = Array.from({ length: highlights }, () => {
    let i = 0, j = 0;
    for (let n = 0; n < 20; n++) {
      i = Math.floor((rnd() - 0.5) * 16);
      j = Math.floor((rnd() - 0.5) * 9);
      // keep the very middle free for the headline
      if (!used.has(`${i},${j}`) && !(Math.abs(i + 0.5) < 5 && Math.abs(j + 0.5) < 3)) break;
    }
    used.add(`${i},${j}`);
    return { i, j, dur: round(5 + rnd() * 4, 1), delay: round(-rnd() * 9, 1), still: round(0.4 + rnd() * 0.45) };
  });

  const mask = FADE[fade];
  const gap = variant === 'squares' ? Math.max(3, Math.round(cell / 12)) : 0;
  const inset = gap / 2;

  return (
    <div ref={ref} aria-hidden data-clip-ok data-scroll={scrollReveal ? '' : undefined} className={cn('kitfx-grid pointer-events-none absolute inset-0 overflow-hidden', className)} style={mask ? { maskImage: mask, WebkitMaskImage: mask } : undefined}>
      {(highlights > 0 || scrollReveal) && <style dangerouslySetInnerHTML={{ __html: CSS }} />}
      <svg className="absolute inset-0 size-full text-[color-mix(in_oklch,var(--foreground)_9%,transparent)] dark:text-[color-mix(in_oklch,var(--foreground)_10%,transparent)]">
        <defs>
          <pattern id={id} width={cell} height={cell} patternUnits="userSpaceOnUse" x="50%" y="50%">
            {variant === 'squares' ? (
              <rect x={inset} y={inset} width={cell - gap} height={cell - gap} rx={Math.round(cell / 9)} fill="currentColor" fillOpacity={0.45} stroke="currentColor" />
            ) : (
              <path d={`M ${cell} 0.5 H 0.5 V ${cell}`} fill="none" stroke="currentColor" strokeWidth={1} strokeDasharray={variant === 'dashed' ? '3 5' : undefined} />
            )}
          </pattern>
        </defs>
        <rect width="100%" height="100%" fill={`url(#${id})`} />
        {cells.length > 0 && (
          <svg x="50%" y="50%" overflow="visible">
            {cells.map((c, k) => (
              <rect
                key={k}
                x={c.i * cell + (variant === 'squares' ? inset : 1)}
                y={c.j * cell + (variant === 'squares' ? inset : 1)}
                width={variant === 'squares' ? cell - gap : cell - 1}
                height={variant === 'squares' ? cell - gap : cell - 1}
                rx={variant === 'squares' ? Math.round(cell / 9) : 0}
                className="fill-[color-mix(in_oklab,var(--primary)_14%,transparent)] stroke-[color-mix(in_oklab,var(--primary)_40%,transparent)] dark:fill-[color-mix(in_oklab,var(--primary)_22%,transparent)] dark:stroke-[color-mix(in_oklab,var(--primary)_55%,transparent)]"
                style={{ opacity: c.still, animation: pulse ? `kitfx-grid-pulse ${c.dur}s ease-in-out ${c.delay}s infinite` : undefined }}
              />
            ))}
          </svg>
        )}
      </svg>
    </div>
  );
}

Häufige Fragen

Wie installiere ich Raster mit Verlauf?

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

Welche Varianten hat Raster mit Verlauf?

3 Varianten, Linien mit Puls, Kacheln und Gestrichelt von oben. Du wählst sie mit der Prop variant, zum Beispiel variant="squares".

Kann ein KI-Assistent Raster mit Verlauf einbauen?

Ja. Über den MCP-Server der aiw CLI finden Claude, Cursor, GitHub Copilot, Windsurf, Codex und Gemini Raster mit Verlauf mit aiw_components und installieren sie mit aiw_add @ai-website/grid-fade. Nenn im Prompt die Variante, etwa variant="lines", dann schreibt der Assistent deine Inhalte direkt in die Props.

Oft gesucht

Direkt zu

In die Zwischenablage kopiert