SVG Text Draw

PreviousNext

SVG path drawing animation that reveals text letter by letter with stroke animation

Docs
animbitscomponent

Preview

Loading preview…
registry/new-york/animations/text/svg-text-draw.tsx
"use client";
import * as React from "react";
import { motion, SVGMotionProps } from "motion/react";
import { cn } from "@/lib/utils";

export interface SvgTextDrawProps extends Omit<SVGMotionProps<SVGSVGElement>, "children"> {
    children: string;
    delay?: number;
    speed?: number;
    className?: string;
}

// Simple letter path definitions (lowercase letters)
const letterPaths: Record<string, string> = {
    a: "M 10 30 Q 10 10, 25 10 Q 40 10, 40 25 Q 40 40, 25 40 Q 15 40, 10 35 M 40 10 L 40 40",
    b: "M 10 0 L 10 40 M 10 20 Q 10 10, 20 10 Q 30 10, 30 25 Q 30 40, 20 40 Q 10 40, 10 35",
    c: "M 35 15 Q 30 10, 20 10 Q 10 10, 10 25 Q 10 40, 20 40 Q 30 40, 35 35",
    d: "M 40 0 L 40 40 M 40 10 Q 40 10, 25 10 Q 10 10, 10 25 Q 10 40, 25 40 Q 40 40, 40 35",
    e: "M 35 25 L 10 25 Q 10 10, 25 10 Q 40 10, 40 25 Q 40 40, 25 40 Q 10 40, 10 35",
    f: "M 30 5 Q 25 0, 20 0 M 20 0 L 20 40 M 10 15 L 30 15",
    g: "M 40 10 Q 40 10, 25 10 Q 10 10, 10 25 Q 10 40, 25 40 Q 40 40, 40 30 L 40 45 Q 40 50, 25 50",
    h: "M 10 0 L 10 40 M 10 20 Q 10 10, 20 10 Q 30 10, 30 20 L 30 40",
    i: "M 20 5 L 20 8 M 20 15 L 20 40",
    j: "M 25 5 L 25 8 M 25 15 L 25 40 Q 25 50, 15 50",
    k: "M 10 0 L 10 40 M 30 15 L 10 25 L 30 40",
    l: "M 20 0 L 20 40",
    m: "M 10 40 L 10 10 Q 10 10, 17 10 Q 24 10, 24 17 L 24 40 M 24 10 Q 24 10, 31 10 Q 38 10, 38 17 L 38 40",
    n: "M 10 40 L 10 10 Q 10 10, 20 10 Q 30 10, 30 20 L 30 40",
    o: "M 25 10 Q 10 10, 10 25 Q 10 40, 25 40 Q 40 40, 40 25 Q 40 10, 25 10",
    p: "M 10 50 L 10 10 Q 10 10, 20 10 Q 30 10, 30 25 Q 30 40, 20 40 Q 10 40, 10 35",
    q: "M 40 10 Q 40 10, 25 10 Q 10 10, 10 25 Q 10 40, 25 40 Q 40 40, 40 35 L 40 50",
    r: "M 10 40 L 10 10 Q 10 10, 20 10 Q 30 10, 30 15",
    s: "M 35 15 Q 30 10, 20 10 Q 10 10, 10 17 Q 10 25, 30 25 Q 40 25, 40 33 Q 40 40, 20 40 Q 10 40, 10 35",
    t: "M 20 5 L 20 35 Q 20 40, 25 40 M 10 15 L 30 15",
    u: "M 10 10 L 10 30 Q 10 40, 20 40 Q 30 40, 30 30 L 30 10",
    v: "M 10 10 L 20 40 L 30 10",
    w: "M 10 10 L 15 40 L 25 10 L 30 40 L 35 10",
    x: "M 10 10 L 30 40 M 30 10 L 10 40",
    y: "M 10 10 L 20 30 L 30 10 M 20 30 L 20 45 Q 20 50, 10 50",
    z: "M 10 10 L 30 10 L 10 40 L 30 40",
    " ": "",
};

const letterWidth = 50; // Width allocated for each letter

export function SvgTextDraw({
    children,
    className,
    delay = 0,
    speed = 1,
    ...props
}: SvgTextDrawProps) {
    // Convert children to string and lowercase
    const text = String(children).toLowerCase();
    const totalWidth = text.length * letterWidth;

    // Higher speed = faster animation (divide duration by speed)
    const calc = (x: number) => x / speed;

    return (
        <motion.svg
            className={cn("h-12", className)}
            xmlns="http://www.w3.org/2000/svg"
            viewBox={`0 0 ${totalWidth} 60`}
            fill="none"
            stroke="currentColor"
            strokeWidth="3"
            strokeLinecap="round"
            strokeLinejoin="round"
            initial={{ opacity: 1 }}
            animate={{ opacity: 1 }}
            {...props}
        >
            <title>{children}</title>
            {text.split("").map((char, index) => {
                const path = letterPaths[char] || "";
                if (!path) return null;

                const xOffset = index * letterWidth;
                const charDelay = delay + (index * 0.15) / speed;

                return (
                    <motion.path
                        key={`${char}-${index}`}
                        d={path}
                        transform={`translate(${xOffset}, 0)`}
                        initial={{ pathLength: 0, opacity: 0 }}
                        animate={{ pathLength: 1, opacity: 1 }}
                        transition={{
                            pathLength: {
                                duration: calc(0.5),
                                ease: "easeInOut",
                                delay: calc(charDelay),
                            },
                            opacity: {
                                duration: calc(0.3),
                                delay: calc(charDelay),
                            },
                        }}
                    />
                );
            })}
        </motion.svg>
    );
}

Installation

npx shadcn@latest add @animbits/text-svg-text-draw

Usage

import { TextSvgTextDraw } from "@/components/text-svg-text-draw"
<TextSvgTextDraw />