Signature
spell-ui/signatureFreeComponent
Animated signature component with handwriting effect using custom fonts.
Live preview
live · rendered by the registry
Rendered by spell-ui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://spell.sh/r/signature.jsonSource
1"use client";23import { useEffect, useId, useState } from "react";4import { motion } from "motion/react";5import { parse as parseFont } from "opentype.js";67type SignatureGlyph = {8 advanceWidth?: number;9 getPath: (10 x: number,11 y: number,12 fontSize: number,13 ) => {14 toPathData: (decimalPlaces?: number) => string;15 };16};1718type SignatureFont = {19 unitsPerEm: number;20 charToGlyph: (char: string) => SignatureGlyph;21};2223const SVG_HEIGHT = 100;24const PATH_DELAY_STEP = 0.2;25const OPACITY_DELAY_OFFSET = 0.01;26const fontCache = new Map<string, SignatureFont>();2728function getFontCacheKey(path: string): string {29 try {30 return new URL(path, window.location.origin).href;31 } catch {32 return path;33 }34}3536function getPathTransition(index: number, duration: number, delay: number) {37 const pathDelay = delay + index * PATH_DELAY_STEP;3839 return {40 pathLength: {41 delay: pathDelay,42 duration,43 ease: "easeInOut" as const,44 },45 opacity: {46 delay: pathDelay + OPACITY_DELAY_OFFSET,47 duration: 0.01,48 },49 };50}5152async function loadFontFromPaths(fontPaths: string[]): Promise<SignatureFont> {53 for (const path of fontPaths) {54 try {55 const cacheKey = getFontCacheKey(path);56 const cachedFont = fontCache.get(cacheKey);5758 if (cachedFont) {59 return cachedFont;60 }6162 const response = await fetch(path);6364 if (!response.ok) {65 continue;66 }6768 const fontBuffer = await response.arrayBuffer();69 const font = parseFont(fontBuffer) as SignatureFont;70 fontCache.set(cacheKey, font);7172 return font;73 } catch {74 // Try next path75 }76 }7778 throw new Error(79 `Font could not be loaded from the provided path${fontPaths.length === 1 ? "" : "s"}: ${fontPaths.join(", ")}`,80 );81}8283async function buildSignaturePaths({84 text,85 fontSize,86 baseline,87 horizontalPadding,88}: {89 text: string;90 fontSize: number;91 baseline: number;92 horizontalPadding: number;93}): Promise<{ paths: string[]; width: number }> {94 const font = await loadFontFromPaths(["/LastoriaBoldRegular.otf"]);9596 let x = horizontalPadding;97 const nextPaths: string[] = [];9899 for (const char of text) {100 const glyph = font.charToGlyph(char);101 const path = glyph.getPath(x, baseline, fontSize);102 nextPaths.push(path.toPathData(3));103104 const advanceWidth = glyph.advanceWidth ?? font.unitsPerEm;105 x += advanceWidth * (fontSize / font.unitsPerEm);106 }107108 return {109 paths: nextPaths,110// … truncated
What it pulls in
npm packages
Files it writes
More from spell-ui
All 33 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Animated Checkboxanimated-checkbox | spell-ui | Components | Free | 1 dep | |
| Animated Gradientanimated-gradient | spell-ui | Components | Free | no deps | |
| Badgebadge | spell-ui | Components | Free | 2 deps | |
| Bars Spinnerbars-spinner | spell-ui | Components | Free | no deps | |
| Blur Revealblur-reveal | spell-ui | Components | Free | 1 dep | |
| Chartchart | spell-ui | Components | Free | no deps | |
| Color Selectorcolor-selector | spell-ui | Components | Free | no deps | |
| Copy Buttoncopy-button | spell-ui | Components | Free | 1 dep |
