25
Componentregistry:component

Etching

Renders an image as a copperplate engraving on canvas: hatch lines that bow around the forms and swell with the shadows, cross-hatching in the darks, a third pass in the deepest shadows, and ink on paper with plate tone. The lines cut themselves in, stroke by stroke, when it scrolls into view, or follow a controlled progress value.

Installation

$ npx shadcn@latest add @creative-ui/etching

On an older shadcn CLI, use the URL: npx shadcn@latest add https://ui.krutikraut.com/r/etching.json

Preview

Line spacing4px
Angle35°
Layers
Ink

Usage

Example.tsx
import { Etching } from "@/components/etching"

export function Plate() {
  return (
    <Etching
      src="/marble-bust.webp"
      alt="Marble bust of a woman in profile"
      className="aspect-[4/5] w-80"
    />
  )
}

// Finer lines in oxblood, straight-ruled, drawn as the reader scrolls.
<Etching src="/still-life.jpg" lineSpacing={3} curve={0} ink="#6e2a24" progress={scrollProgress} className="w-full" />

Props

PropTypeDefaultDescription
src*string—Image URL. Cross-origin images must be served with CORS headers, or the canvas cannot read them.
altstring""Accessible description. Leave empty for decorative images.
lineSpacingnumber4Gap between hatch lines in CSS px. Smaller is finer and takes longer to lay out.
anglenumber35Angle of the first pass of lines, in degrees.
curvenumber0.5How much the lines bow and swerve around the light and shadow of the image, 0 to 1. 0 is ruled straight.
layers1 | 2 | 33Passes of the burin. 1 hatches only. 2 adds cross-hatching in the darker tones. 3 adds a third pass in the deepest shadows.
inkstring"#241d17"Ink colour. Any CSS colour, or currentColor to follow the theme.
paperstring"#f4f1ea"Paper colour, or transparent.
drawbooleantrueCut the lines in the first time it scrolls into view: the first pass, then the cross-hatching, stroke by stroke. Plays once, pauses off screen, and not at all for reduced motion.
durationnumber2600Length of the engraving in ms.
progressnumber—Controlled progress, 0 to 1, for driving it from scroll or a slider. When set, it replaces the scroll-in animation.
fit"cover" | "contain""cover"How the image fills the plate.
seednumber5Seed for the hand tremor and the stroke order.
crossOrigin"anonymous" | "use-credentials""anonymous"Pass undefined for same-origin images that must not send a CORS request.
childrenReact.ReactNode—Content layered over the plate, such as a caption.

Also accepts every div prop, which is spread onto the root element.

Notes

  • Without an aspect-* class or a height it takes the image's own aspect ratio once the image loads.
  • It draws to a canvas, so the image must be same-origin or served with CORS headers. If the pixels cannot be read, it leaves bare paper and logs a warning.
  • Each frame inks only the newly cut stretch of each line, so the animation costs about the same as drawing the plate once. Moving progress backwards redraws from blank paper.
  • Images with a clear subject and a plain ground read best. Busy photos turn into dense cross-hatching at small sizes; raise lineSpacing or lower layers.
  • Changing an option after the engraving has played redraws it at once instead of replaying. To replay, remount the component with a new key.
  • The canvas has role="img" and alt as its label. With an empty alt it is hidden from assistive tech.

Dependencies

shadcn/utils