reelcn
GitHubBack to landing

Svg draw

Draws any set of SVG paths on stroke by stroke, staggered, with an optional fill fade once each finishes.

SvgDraw, check
SvgDraw, check thumbnail, 16:9

Installation

npx shadcn add https://www.reelcn.dev/r/svg-draw.json

Files land in src/reelcn/ with the items it builds on.

Usage

<Center>
  <SvgDraw viewBox="0 0 24 24" paths={["M4 12h16", "M4 6h10", "M4 18h7"]} size={220} />
</Center>

Props

PropTypeDescription
delaynumberFrames to wait before entering.
durationnumberEnter duration in frames. Defaults to 0.6s. Some items reuse duration for an unrelated local timing. See that item's own prop docs.
exitnumber | booleanExit at the end of the parent `<Sequence>`. false keeps it on screen, a number sets the exit length in frames.
motionMotionPersonalityOverride the theme's motion personality.
posterbooleanRenders fully entered, with no exit: for a still frame or thumbnail. Defaults to false.
holdFramesnumberDelays the start of the exit window by this many frames, for a minimum on-screen hold. Defaults to 0.
paths*string[]One or more d attributes, drawn in order.
viewBox*stringMust be space- or comma-separated: `"minX minY width height"`.
colorstringStroke color. Defaults to the theme foreground.
strokeWidthnumberIn `viewBox` units, not design units, it scales with the drawing, like an icon's stroke. Default 3.
stepnumberFrames between one path's draw and the next. Defaults to 0.15s.
sizenumberRendered width in design units. Height follows the `viewBox` ratio. Default 200.
fillstringFades a fill in behind each path once it finishes drawing. Omit for a stroke-only line drawing.
styleCSSProperties
classNamestring

Built from

Use

  • A logo mark, icon or custom illustration drawing itself on screen
  • Sketching a diagram or shape that a `callout` or label then points at

Avoid

Guides

Components take their colors from a theme, their timing from motion props and their layout from the canvas.

Source

registry/items/svg-draw.tsx
/**
 * @title Svg Draw
 * @category product
 * @description Draws any set of SVG paths on stroke by stroke, staggered, with an optional fill fade once each finishes.
 * @duration data-driven
 * @use A logo mark, icon or custom illustration drawing itself on screen
 * @use Sketching a diagram or shape that a `callout` or label then points at
 * @avoid A single hand-drawn circle or arrow. Use `scribble-circle` or `arrow`
 * @tags svg, draw, path, icon, illustration, line art
 * @example
 * <Center>
 *   <SvgDraw viewBox="0 0 24 24" paths={["M4 12h16", "M4 6h10", "M4 18h7"]} size={220} />
 * </Center>
 */
import type React from "react";
import { type MotionProps, StrokeOverlay, tween, useMotion, useTheme, useViewport } from "./core";

export type SvgDrawProps = MotionProps & {
  /** One or more `d` attributes, drawn in order. */
  paths: string[];
  /** Must be space- or comma-separated: `"minX minY width height"`. */
  viewBox: string;
  /** Stroke color. Defaults to the theme foreground. */
  color?: string;
  /** In `viewBox` units, not design units, it scales with the drawing, like an icon's stroke. */
  strokeWidth?: number;
  /** Frames between one path's draw and the next. Defaults to 0.15s. */
  step?: number;
  /** Rendered width in design units. Height follows the `viewBox` ratio. */
  size?: number;
  /** Fades a fill in behind each path once it finishes drawing. Omit for a stroke-only line drawing. */
  fill?: string;
  style?: React.CSSProperties;
  className?: string;
};

const clamp01 = (value: number) => Math.min(Math.max(value, 0), 1);

export function SvgDraw({
  paths,
  viewBox,
  color,
  strokeWidth = 3,
  step,
  size = 200,
  fill,
  style,
  className,
  ...motion
}: SvgDrawProps) {
  const theme = useTheme();
  const { u } = useViewport();
  const m = useMotion(motion);
  const stroke = color ?? theme.colors.foreground;
  const frames = step ?? Math.round(m.fps * 0.15);
  const box = viewBox.split(/[\s,]+/).map(Number);
  const vbWidth = box[2];
  const vbHeight = box[3];
  const width = u(size);
  const height = vbWidth > 0 ? width * (vbHeight / vbWidth) : width;

  return (
    <svg
      aria-hidden="true"
      className={className}
      width={width}
      height={height}
      viewBox={viewBox}
      style={{ opacity: 1 - m.exit, overflow: "visible", ...style }}
    >
      {paths.map((d, index) => {
        const drawn = clamp01(
          tween(m.frame, m.fps, { from: m.delay + index * frames, duration: m.enterFrames, motion: m.preset }),
        );
        const filled = fill
          ? clamp01(
              tween(m.frame, m.fps, {
                from: m.delay + index * frames + m.enterFrames,
                duration: Math.round(m.enterFrames * 0.6),
                motion: "smooth",
              }),
            )
          : 0;
        return (
          <g key={index} opacity={drawn > 0 ? 1 : 0}>
            {fill ? <path d={d} fill={fill} fillOpacity={filled} stroke="none" /> : null}
            <StrokeOverlay
              d={d}
              kind={theme.stroke}
              seed={`svg-draw-${index}`}
              color={stroke}
              strokeWidth={strokeWidth}
              drawn={drawn}
              extraProps={{ strokeLinecap: "round", strokeLinejoin: "round" }}
            />
          </g>
        );
      })}
    </svg>
  );
}

On this page