Skip to content

Rough Notation

@rougher-stuff/rough-notation draws animated annotations around elements, text ranges, collections, and virtual targets. It also provides shared layers, connectors, and composed callouts.

Basic annotation

ts
import { annotate } from '@rougher-stuff/rough-notation';

const annotation = annotate(target, {
  type: 'underline',
  color: '#cc5533',
  seed: 42,
});

const controller = annotation.show({ autoplay: false });
controller.seek(0.4).play();
await controller.finished;

Annotation types include underline, box, circle, highlight, strike-through, crossed-off, bracket, and sun-lines.

Targets and layers

Text effects preserve line fragments while shape effects use union bounds by default. Set rectMode to segments or union to override aggregation.

Layer surfaces are shared by root, placement, z-index, and name. They update on resize and scroll, ignore pointer events, and are reference-counted until the last annotation is removed.

Connectors

ts
import { annotateArrow, annotateComment } from '@rougher-stuff/rough-notation/connectors';

const arrow = annotateArrow({
  from: commentElement,
  to: targetElement,
  route: { type: 'swoop' },
  end: { type: 'open-arrow' },
  seed: 42,
});

arrow.show();

annotateComment({
  comment: commentElement,
  target: targetElement,
  connector: { type: 'underline-arrow', route: { type: 'pigtail' } },
}).show();

Callouts

annotateCallout() sequences an optional marker, target emphasis, connector, and content reveal on one shared timeline. Markers support asterisks, dots, and numbers.

Hide effects

ts
annotation.hide({ effect: 'reverse-draw' });
annotation.hide({
  effect: 'erase',
  erase: { origin: 'top-left', width: 12, softness: 1, roughness: 2 },
});

The eraser uses a cumulative rough sweep, so regions remain erased as its leading edge moves across the annotation.

See the package README for target styling, groups, and compatibility APIs.

Released under the MIT License.