Atrament
A small JS library for beautiful drawing and handwriting on the HTML Canvas

Atrament is a library for drawing and handwriting on the HTML canvas.
Its goal is for drawing to feel natural and comfortable, and the result to be smooth and pleasing.
Atrament does not store the stroke paths itself - instead, it draws directly onto the canvas bitmap,
just like an ink pen onto a piece of paper (“atrament” means ink in Slovak and Polish).
This makes it suitable for certain applications, and not quite ideal for others - see Alternatives.
⚠️ Note: From version 4, Atrament supports evergeen browsers (Firefox, Chrome and Chromium-based browsers)
and Safari 15 or above. If your application must support older browsers, please use version 3. You can view the v3 documentation here.
Features:
- Draw/Fill/Erase modes
- Adjustable adaptive smoothing
- Events tracking the drawing - this allows the app to “replay” or reconstruct the drawing, e.g. for undo functionality
- Adjustable line thickness and colour
Here’s a basic demo.
Enjoy!
Installation
If you’re using a tool like rollup or webpack to bundle your code, you can install it using npm.
- install atrament as a dependency using
npm install --save atrament.
- You can access the Atrament class using
import { Atrament } from 'atrament';
Usage
- create a
<canvas> tag, e.g.:
<canvas id="sketchpad" width="500" height="500"></canvas>
- in your JavaScript, create an
Atrament instance passing it your canvas object:
import Atrament from 'atrament';
const canvas = document.querySelector('#sketchpad');
const sketchpad = new Atrament(canvas);
const sketchpad = new Atrament(canvas, {
width: 500,
height: 500,
color: 'orange',
});
- that’s it, happy drawing!
Options & config
sketchpad.clear();
- change the line thickness:
sketchpad.weight = 20;
sketchpad.color = '#ff485e';
- toggle between modes (Note: for Fill mode, you must also set the
fillWorker config option in the constructor. See next section)
import { MODE_DRAW, MODE_ERASE, MODE_FILL, MODE_DISABLED } from 'atrament';
sketchpad.mode = MODE_DRAW;
sketchpad.mode = MODE_ERASE;
sketchpad.mode = MODE_FILL;
sketchpad.mode = MODE_DISABLED;
- tweak smoothing - higher values make the drawings look smoother, lower values make drawing feel a bit more responsive. Set to
0.85 by default.
sketchpad.smoothing = 1.3;
- toggle adaptive stroke, i.e. line width changing based on drawing speed and stroke progress. This simulates the variation in ink discharge of a physical pen.
true by default.
sketchpad.adaptiveStroke = false;
- set pressure sensitivity. Note: if your input device sends pressure data, adaptive stroke will have no effect, since its purpose is to emulate changing pen pressure
sketchpad.pressureLow = 0;
sketchpad.pressureHigh = 2;
sketchpad.pressureSmoothing = 0.4;
- the
secondaryMouseButton option enables drawing using the secondary (right) mouse button - e.g. as a quick eraser. This false by default.
sketchpad.secondaryMouseButton = true;
- ignore strokes with modifier keys pressed (e.g. Alt/Ctrl/Cmd/Windows key).
false by default.
sketchpad.ignoreModifiers = true;
- record stroke data (enables the
strokerecorded event). false by default.
sketchpad.recordStrokes = true;
Fill mode
From version 5.0.0, Atrament will not bundle the fill Worker within the main bundle. This is so applications that don’t require fill mode
benefit from an approx. 60% smaller import size. The fill module can be imported separately and injected into Atrament via the constructor:
import Atrament from 'atrament';
import fill from 'atrament/fill';
const sketchpad = new Atrament({ fill });
Data model
- Atrament models its output as a set of independent strokes. Only one stroke can be drawn at a time.
- Each stroke consists of a list of segments, which correspond to all the pointer positions recorded during drawing.
- Each segment consists of a point which contains
x and y coordinates, a time which is the number of milliseconds since the stroke began, until the segment was drawn, and a pressure value (0.-1.) which is either the recorded stylus pressure or 0.5 if no pressure data is available.
- Each stroke also contains information about the drawing settings at the time of drawing (see Events > Stroke recording).
Events
Dirty/clean
These events fire when the canvas is first drawn on, and when it’s cleared.
The state is stored in the dirty property.
sketchpad.addEventListener('dirty', () => console.info(sketchpad.dirty));
sketchpad.addEventListener('clean', () => console.info(sketchpad.dirty));
Stroke start/end
These events inform that a stroke has started/finished. They also return x and y properties
denoting where on the canvas the event occurred.
sketchpad.addEventListener('strokestart', () => console.info('strokestart'));
sketchpad.addEventListener('strokeend', () => console.info('strokeend'));
Fill start/end
These only fire in fill mode. The fillstart event also contains x and y properties
denoting the starting point of the fill operation (where the user has clicked).
sketchpad.addEventListener('fillstart', ({ x, y }) =>
console.info(`fillstart ${x} ${y}`),
);
sketchpad.addEventListener('fillend', () => console.info('fillend'));
Pointer down/up
Sometimes you might want to tweak Atrament’s settings as soon as the user begins/ends a stroke,
but before Atrament actually draws anything. The pointerdown/up events allow you to do this.
The argument is the PointerEvent itself.
sketchpad.addEventListener('pointerdown', (event) => console.info('pointerdown', event));
sketchpad.addEventListener('pointerup', (event) => console.info('pointerup', event));
Stroke recording
The following events only fire if the recordStrokes property is set to true.
strokerecorded fires at the same time as strokeend and contains data necessary for reconstructing the stroke.
segmentdrawn fires during stroke recording every time the draw method is called. It contains the same data as strokerecorded.
sketchpad.addEventListener('strokerecorded', ({ stroke }) =>
console.info(stroke),
);
sketchpad.addEventListener('segmentdrawn', ({ stroke }) =>
console.info(stroke),
);
Programmatic drawing
To enable functionality such as undo/redo, stroke post-processing, and SVG export in apps using Atrament, the library
can be configured to record and programmatically draw the strokes.
The first step is to enable recordStrokes, and add a listener for the strokerecorded event:
atrament.recordStrokes = true;
atrament.addEventListener('strokerecorded', ({ stroke }) => {
});
The stroke can then be reconstructed using methods of the Atrament class:
atrament.mode = stroke.mode;
atrament.weight = stroke.weight;
atrament.smoothing = stroke.smoothing;
atrament.color = stroke.color;
atrament.adaptiveStroke = stroke.adaptiveStroke;
const segments = stroke.segments.slice();
const firstPoint = segments.shift().point;
atrament.beginStroke(firstPoint.x, firstPoint.y);
let prevPoint = firstPoint;
while (segments.length > 0) {
const segment = segments.shift();
const { x, y } = atrament.draw(segment.point.x, segment.point.y, prevPoint.x, prevPoint.y, segment.pressure);
prevPoint = { x, y };
}
atrament.endStroke(prevPoint.x, prevPoint.y);
Implementing Undo/Redo
Atrament does not provide its own undo/redo functionality to keep the scope as small as possible. However, using stroke recording and programmatic drawing,
it is possible to implement undo/redo with a relatively small amount of code. See @nidoro and @feored’s example here.
Development
To obtain the dependencies, cd into the atrament directory and run npm install.
You should be able to then build atrament by simply running npm run build and rebuild continuously with npm run watch.
Running the demo locally
The demo app is useful for development, and it’s set up to use the compiled files in /dist. It’s a plain HTML website which can be served with any local server.
A good way to develop using the demo is to run python -m http.server (with Python 3) in the /demo directory. The demo will be served on localhost:8000.
Alternatives
Atrament’s philosophy is to provide a simple and small tool that takes care of everything from pointer events to drawing pixels on screen.
Atrament uses the native Canvas API to draw strokes, instead of computing custom curves. This means it’s very lightweight (5.9kB gzipped with fill mode, 2.4kB without)
and pretty much as fast as the browser allows.
This does mean Atrament’s rendering quality is limited by the Canvas API. If your application requires higher drawing quality, there are libraries such as
perfect-freehand which compute their own curves and achieve somewhat more pleasing, higher-fidelity results.
This comes at the expense of size (perfect-freehand is almost 2kB gzipped to generate the curve shape, but you need to take care of rendering it, handling pointer interactions, etc.).
For a more fully-featured solution including drawing shapes, graphs, text, built-in Undo/Redo and many other features,
you might want to consider a larger tool such as excalidraw.
评论