Hand-Drawn Text Annotations for Svelte – svelte-annotate

Draw sketchy text marks in Svelte with seven annotation types, timed sequences, multiline handling, RTL drawing, and scoped clocks.

svelte-annotate is a Svelte annotation that draws hand-drawn underlines, highlights, circles, boxes, strike-throughs, crossed-off marks, and brackets around text.

Each mark follows a numeric clock. You can control when the drawing begins and how long it takes.

Features

  • 7 annotation types: underline, box, circle, highlight, strike-through, crossed-off, and bracket.
  • <Mark> component, Svelte attach syntax, and prop spread syntax.
  • Clock-based drawing sequences with configurable start and duration values.
  • Shared clocks for related marks and scoped clocks for independent animations.
  • Configurable color, padding, line thickness, roughness, seed, and stroke passes.
  • Per-line annotations for wrapped text through multiline.
  • Right-to-left drawing through the rtl option.
  • Custom wrapper elements plus standard element attributes and event handlers.

Use Cases

  • Documentation pages that underline terminology or circle important values as an explanation progresses.
  • Product walkthroughs where several callouts draw in a controlled sequence.
  • Educational interfaces that reveal emphasis alongside lesson progress.
  • Presentation-style screens with timed highlights, strike-throughs, and brackets.
  • Landing-page headings that use sketch-like marks for selected words or phrases.

How To Use It

Install the package:

npm i @sveltecraft/annotate

Basic Usage

Create a Tween, register it as the annotation clock, and render a <Mark>:

<script>
  import { Tween } from 'svelte/motion';
  import { Mark, annotate } from '@sveltecraft/annotate';
  const clock = new Tween(0, { duration: 2000 });
  annotate(clock);
</script>
<button onclick={() => (clock.target = 2)}>
  Draw annotation
</button>
<p>
  Svelte makes
  <Mark type="underline" color="#f97316" at={0}>
    reactive interfaces
  </Mark>
  concise.
</p>

Sequence Multiple Annotations

The at value sets the clock position where a mark begins. span controls how many clock units the drawing consumes.

Set the clock to 2 for two one-unit annotations:

<script>
  import { Tween } from 'svelte/motion';
  import { Mark, annotate } from '@sveltecraft/annotate';
  const clock = new Tween(0, { duration: 2400 });
  annotate(clock);
</script>
<button onclick={() => (clock.target = 2)}>
  Play sequence
</button>
<p>
  <Mark type="underline" color="#2563eb" at={0} span={1}>
    Install the package
  </Mark>
  and
  <Mark type="circle" color="#dc2626" at={1} span={1}>
    animate the key phrase
  </Mark>.
</p>

Use Attach and Spread Syntax

Create a scoped annotator when you want to attach marks directly to existing elements:

<script>
  import { Tween } from 'svelte/motion';
  import { annotate } from '@sveltecraft/annotate';
  const clock = new Tween(0, { duration: 2000 });
  const mark = annotate.scoped(clock);
</script>
<button onclick={() => (clock.target = 2)}>
  Draw
</button>
<p>
  <span
    {@attach mark({
      type: 'underline',
      color: '#f97316',
      at: 0
    })}
  >
    Attach syntax
  </span>
  <span
    {...mark({
      type: 'circle',
      color: '#8b5cf6',
      at: 1
    })}
  >
    Spread syntax
  </span>
</p>

Choose the Element Rendered by Mark

<Mark> renders a span by default. Set as when the annotation needs another element:

<Mark
  as="div"
  type="box"
  color="#16a34a"
  class="feature-note"
  at={0}
>
  This annotation renders as a div.
</Mark>

Run Independent Annotation Sequences

One annotate() call establishes a shared clock for plain <Mark> components. Use annotate.scoped() when different groups need their own timing:

<script>
  import { Tween } from 'svelte/motion';
  import { Mark, annotate } from '@sveltecraft/annotate';
  const headingClock = new Tween(0, { duration: 1200 });
  const noteClock = new Tween(0, { duration: 1800 });
  const headingMark = annotate.scoped(headingClock);
  const noteMark = annotate.scoped(noteClock);
</script>
<button onclick={() => (headingClock.target = 1)}>
  Draw heading
</button>
<button onclick={() => (noteClock.target = 1)}>
  Draw note
</button>
<h2>
  <Mark
    mark={headingMark}
    type="underline"
    color="#2563eb"
  >
    Product update
  </Mark>
</h2>
<p>
  <Mark
    mark={noteMark}
    type="highlight"
    color="#fde047"
  >
    Available now
  </Mark>
</p>

Clock Inputs

annotate() accepts four clock shapes:

Clock InputExampleBehavior
Fixed numberannotate(2)Uses a fixed clock value.
Getter functionannotate(() => value)Reads the returned value on each update.
Svelte Tweenannotate(clock)Reads clock.current.
Object with a clockannotate(scene)Reads scene.clock.

Annotation Options

OptionDefaultDescription
typeRequiredunderline, box, circle, highlight, strike-through, crossed-off, or bracket.
colorcurrentColorStroke color.
padding5Gap around the content. Brackets default to 12.
strokeWidth2.5Line thickness. Ignored by highlight.
roughness1.6Stroke wobble. Set 0 for straight lines.
seed7Fixed drawing seed used across repaints.
iterations2Number of stroke passes.
rtlfalseDraws from right to left.
brackets['right']Bracket sides to draw.
multilinefalseDraws a mark for each wrapped line.
at0Clock value where drawing begins.
span1Clock units used by the drawing.

Mark-Specific Props

PropPurpose
markAssigns an explicit scoped annotation clock.
asSelects the rendered HTML element. Defaults to span.
classPasses a CSS class to the rendered element.
Standard element propsAccepts values such as id, style, data-*, and handlers.

Annotate Multiline Text

Set multiline={true} when wrapped text needs an annotation on each rendered line:

<Mark
  type="highlight"
  color="#fef08a"
  multiline={true}
  padding={2}
>
  This longer sentence can wrap across several lines while each line receives
  its own hand-drawn highlight.
</Mark>

Draw Right to Left

Set rtl for annotations that need the opposite drawing direction:

<Mark
  type="underline"
  color="#0f766e"
  rtl={true}
>
  Right-to-left drawing
</Mark>

Customize Brackets

The bracket annotation uses the right side by default. Pass the sides you want through brackets:

<Mark
  type="bracket"
  color="#7c3aed"
  brackets={['left', 'right']}
>
  Important side note
</Mark>

Alternatives and Related Resources

FAQs

Q: How do I animate several annotations one after another?
A: Use one clock and assign different at values. Two marks with at={0} and at={1}, each using span={1}, draw consecutively as the clock moves from 0 to 2.

Q: Can two groups of annotations use different animation timing?
A: Yes. Create each group with annotate.scoped(clock) and pass the resulting annotator through the mark prop or spread syntax.

Q: Does svelte-annotate work with wrapped text?
A: Yes. Set multiline={true} to draw individual marks around each rendered line.

Q: Can I annotate an existing element without using the Mark component?
A: Yes. A scoped annotator works through Svelte attach syntax or as a prop spread on an existing element.

Q: What happens if Mark has no clock?
A: A plain <Mark> throws when no shared or explicit clock is available. Register one with annotate(clock) or pass a scoped annotator through mark.

matiadev

matiadev

Front-end developer with a passion for JavaScript and UI/UX design

Leave a Reply

Your email address will not be published. Required fields are marked *