{"id":13445605,"url":"https://github.com/jakubfiala/atrament","last_synced_at":"2026-03-17T20:04:21.149Z","repository":{"id":6344866,"uuid":"55308814","full_name":"jakubfiala/atrament","owner":"jakubfiala","description":"A small JS library for beautiful drawing and handwriting on the HTML Canvas.","archived":false,"fork":false,"pushed_at":"2025-11-25T15:28:54.000Z","size":1289,"stargazers_count":1748,"open_issues_count":2,"forks_count":122,"subscribers_count":29,"default_branch":"main","last_synced_at":"2026-01-30T19:43:37.998Z","etag":null,"topics":["drawing","graphics","html-canvas","javascript"],"latest_commit_sha":null,"homepage":"http://fiala.space/atrament/demo","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jakubfiala.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"jakubfiala"}},"created_at":"2016-04-02T18:02:27.000Z","updated_at":"2026-01-30T01:42:46.000Z","dependencies_parsed_at":"2024-02-20T16:42:40.125Z","dependency_job_id":"00ea8f6e-09d7-41f4-92a9-93b196ebf0f8","html_url":"https://github.com/jakubfiala/atrament","commit_stats":{"total_commits":212,"total_committers":16,"mean_commits":13.25,"dds":"0.37264150943396224","last_synced_commit":"f5483a7424abb8b5f631c154e33ee450955c2698"},"previous_names":["jakubfiala/atrament","jakubfiala/atrament.js"],"tags_count":27,"template":false,"template_full_name":null,"purl":"pkg:github/jakubfiala/atrament","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakubfiala%2Fatrament","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakubfiala%2Fatrament/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakubfiala%2Fatrament/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakubfiala%2Fatrament/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jakubfiala","download_url":"https://codeload.github.com/jakubfiala/atrament/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakubfiala%2Fatrament/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30630038,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-17T17:32:55.572Z","status":"ssl_error","status_checked_at":"2026-03-17T17:32:38.732Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["drawing","graphics","html-canvas","javascript"],"created_at":"2024-07-31T05:00:36.460Z","updated_at":"2026-03-17T20:04:21.131Z","avatar_url":"https://github.com/jakubfiala.png","language":"JavaScript","funding_links":["https://github.com/sponsors/jakubfiala"],"categories":["Libraries","JavaScript","Web Frontend"],"sub_categories":["Canvas draw","Animation \u0026 Canvas"],"readme":"# Atrament\n\n**A small JS library for beautiful drawing and handwriting on the HTML Canvas**\n\n---\n\n![](demo/img/muchotravka.png)\n\nAtrament is a library for drawing and handwriting on the HTML canvas.\nIts goal is for drawing to feel natural and comfortable, and the result to be smooth and pleasing.\nAtrament does not store the stroke paths itself - instead, it draws directly onto the canvas bitmap,\njust like an ink pen onto a piece of paper (\"atrament\" means ink in Slovak and Polish).\nThis makes it suitable for certain applications, and not quite ideal for others - see Alternatives.\n\n⚠️ **Note:** From version 4, Atrament supports evergeen browsers (Firefox, Chrome and Chromium-based browsers)\nand Safari 15 or above. If your application must support older browsers, please use version 3. You can view the v3 documentation [here](https://github.com/jakubfiala/atrament/blob/ded0a8289c7b1ff7a79dbad36893986da09f37fc/README.md).\n\n**Features:**\n\n- Draw/Fill/Erase modes\n- Adjustable adaptive smoothing\n- Events tracking the drawing - this allows the app to \"replay\" or reconstruct the drawing, e.g. for undo functionality\n- Adjustable line thickness and colour\n\n[Here's a basic demo.](https://fiala.space/atrament/demo/)\n\nEnjoy!\n\n- [Atrament](#atrament)\n  - [Installation](#installation)\n  - [Usage](#usage)\n  - [Options \\\u0026 config](#options--config)\n  - [Fill mode](#fill-mode)\n  - [Data model](#data-model)\n  - [High DPI screens](#high-dpi-screens)\n  - [Events](#events)\n    - [Dirty/clean](#dirtyclean)\n    - [Stroke start/end](#stroke-startend)\n    - [Fill start/end](#fill-startend)\n    - [Stroke recording](#stroke-recording)\n  - [Programmatic drawing](#programmatic-drawing)\n    - [Implementing Undo/Redo](#implementing-undoredo)\n  - [Development](#development)\n    - [Running the demo locally](#running-the-demo-locally)\n\n## Installation\n\nIf you're using a tool like `rollup` or `webpack` to bundle your code, you can install it using npm.\n\n- install atrament as a dependency using `npm install --save atrament`.\n- You can access the Atrament class using `import { Atrament } from 'atrament';`\n\n## Usage\n\n- create a `\u003ccanvas\u003e` tag, e.g.:\n\n```html\n\u003ccanvas id=\"sketchpad\" width=\"500\" height=\"500\"\u003e\u003c/canvas\u003e\n```\n\n- in your JavaScript, create an `Atrament` instance passing it your canvas object:\n\n```js\nimport Atrament from 'atrament';\n\nconst canvas = document.querySelector('#sketchpad');\nconst sketchpad = new Atrament(canvas);\n```\n\n- you can also pass the width, height and default colour to the constructor (see [note on high DPI screens](#high-dpi-screens))\n\n```js\nconst sketchpad = new Atrament(canvas, {\n  width: 500,\n  height: 500,\n  color: 'orange',\n});\n```\n\n- that's it, happy drawing!\n\n## Options \u0026 config\n\n- clear the canvas:\n\n```js\nsketchpad.clear();\n```\n\n- change the line thickness:\n\n```js\nsketchpad.weight = 20; //in pixels\n```\n\n- change the color:\n\n```js\nsketchpad.color = '#ff485e'; //just like CSS\n```\n\n- toggle between modes (**Note:** for Fill mode, you must also set the `fillWorker` config option in the constructor. See [next section](#fill-mode))\n\n```js\nimport { MODE_DRAW, MODE_ERASE, MODE_FILL, MODE_DISABLED } from 'atrament';\n\nsketchpad.mode = MODE_DRAW; // default\nsketchpad.mode = MODE_ERASE; // eraser tool\nsketchpad.mode = MODE_FILL; // click to fill area (see next section for more info)\nsketchpad.mode = MODE_DISABLED; // no modification to the canvas (will still fire stroke events)\n```\n\n- tweak smoothing - higher values make the drawings look smoother, lower values make drawing feel a bit more responsive. Set to `0.85` by default.\n\n```js\nsketchpad.smoothing = 1.3;\n```\n\n- 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.\n\n```js\nsketchpad.adaptiveStroke = false;\n```\n\n- 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\n\n```js\n// the lower bound of the pressure scale:\n// at pressure = 0 the stroke width will be multiplied by 0\nsketchpad.pressureLow = 0;\n// the lower bound of the pressure scale:\n// at pressure = 1 the stroke width will be multiplied by 2\nsketchpad.pressureHigh = 2;\n// at pressure = 0.5 the stroke width remains the same\n\n// Amount of low-pass filtering applied to the pressure values.\n// more smoothing might help remove artifacts at the end of strokes\n// where the pressure-sensitive stylus has very low pressure.\n// Range: 0-1 Default: 0.3\nsketchpad.pressureSmoothing = 0.4;\n```\n\n- the `secondaryMouseButton` option enables drawing using the secondary (right) mouse button - e.g. as a quick eraser. This  `false` by default.\n\n```js\nsketchpad.secondaryMouseButton = true;\n```\n\n- ignore strokes with modifier keys pressed (e.g. Alt/Ctrl/Cmd/Windows key). `false` by default.\n\n```js\nsketchpad.ignoreModifiers = true;\n```\n\n- record stroke data (enables the `strokerecorded` event). `false` by default.\n\n```js\nsketchpad.recordStrokes = true;\n```\n\n## Fill mode\n\nFrom 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\nbenefit from an approx. 60% smaller import size. The fill module can be imported separately and injected into Atrament via the constructor:\n\n```js\nimport Atrament from 'atrament';\nimport fill from 'atrament/fill';\n\nconst sketchpad = new Atrament({ fill });\n```\n\n## Data model\n\n- Atrament models its output as a set of independent _strokes_. Only one stroke can be drawn at a time.\n- Each stroke consists of a list of _segments_, which correspond to all the pointer positions recorded during drawing.\n- 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.\n- Each stroke also contains information about the drawing settings at the time of drawing (see Events \u003e Stroke recording).\n\n## Events\n\n### Dirty/clean\n\nThese events fire when the canvas is first drawn on, and when it's cleared.\nThe state is stored in the `dirty` property.\n\n```js\nsketchpad.addEventListener('dirty', () =\u003e console.info(sketchpad.dirty));\nsketchpad.addEventListener('clean', () =\u003e console.info(sketchpad.dirty));\n```\n\n### Stroke start/end\n\nThese events inform that a stroke has started/finished. They also return `x` and `y` properties\ndenoting where on the canvas the event occurred.\n\n```js\nsketchpad.addEventListener('strokestart', () =\u003e console.info('strokestart'));\nsketchpad.addEventListener('strokeend', () =\u003e console.info('strokeend'));\n```\n\n### Fill start/end\n\nThese only fire in fill mode. The `fillstart` event also contains `x` and `y` properties\ndenoting the starting point of the fill operation (where the user has clicked).\n\n```js\nsketchpad.addEventListener('fillstart', ({ x, y }) =\u003e\n  console.info(`fillstart ${x} ${y}`),\n);\nsketchpad.addEventListener('fillend', () =\u003e console.info('fillend'));\n```\n\n### Pointer down/up\n\nSometimes you might want to tweak Atrament's settings as soon as the user begins/ends a stroke,\nbut before Atrament actually draws anything. The `pointerdown/up` events allow you to do this.\nThe argument is the `PointerEvent` itself.\n\n```js\nsketchpad.addEventListener('pointerdown', (event) =\u003e console.info('pointerdown', event));\nsketchpad.addEventListener('pointerup', (event) =\u003e console.info('pointerup', event));\n```\n\n### Stroke recording\n\nThe following events only fire if the `recordStrokes` property is set to true.\n\n`strokerecorded` fires at the same time as `strokeend` and contains data necessary for reconstructing the stroke.\n`segmentdrawn` fires during stroke recording every time the `draw` method is called. It contains the same data as `strokerecorded`.\n\n```js\nsketchpad.addEventListener('strokerecorded', ({ stroke }) =\u003e\n  console.info(stroke),\n);\n/*\n{\n  segments: [\n    {\n      point: { x, y },\n      time,\n      pressure,\n    }\n  ],\n  color,\n  weight,\n  smoothing,\n  adaptiveStroke,\n}\n*/\nsketchpad.addEventListener('segmentdrawn', ({ stroke }) =\u003e\n  console.info(stroke),\n);\n```\n\n## Programmatic drawing\n\nTo enable functionality such as undo/redo, stroke post-processing, and SVG export in apps using Atrament, the library\ncan be configured to record and programmatically draw the strokes.\n\nThe first step is to enable `recordStrokes`, and add a listener for the `strokerecorded` event:\n\n```js\natrament.recordStrokes = true;\natrament.addEventListener('strokerecorded', ({ stroke }) =\u003e {\n  // store `stroke` somewhere\n});\n```\n\nThe stroke can then be reconstructed using methods of the `Atrament` class:\n\n```js\n// set drawing options\natrament.mode = stroke.mode;\natrament.weight = stroke.weight;\natrament.smoothing = stroke.smoothing;\natrament.color = stroke.color;\natrament.adaptiveStroke = stroke.adaptiveStroke;\n\n// don't want to modify original data\nconst segments = stroke.segments.slice();\n\nconst firstPoint = segments.shift().point;\n// beginStroke moves the \"pen\" to the given position and starts the path\natrament.beginStroke(firstPoint.x, firstPoint.y);\n\nlet prevPoint = firstPoint;\nwhile (segments.length \u003e 0) {\n  const segment = segments.shift();\n\n  // the `draw` method accepts the current real coordinates\n  // (i. e. actual cursor position), and the previous processed (filtered)\n  // position. It returns an object with the current processed position.\n  const { x, y } = atrament.draw(segment.point.x, segment.point.y, prevPoint.x, prevPoint.y, segment.pressure);\n\n  // the processed position is the one where the line is actually drawn to\n  // so we have to store it and pass it to `draw` in the next step\n  prevPoint = { x, y };\n}\n\n// endStroke closes the path\natrament.endStroke(prevPoint.x, prevPoint.y);\n```\n\n### Implementing Undo/Redo\n\nAtrament does not provide its own undo/redo functionality to keep the scope as small as possible. However, using stroke recording and programmatic drawing,\nit is possible to implement undo/redo with a relatively small amount of code. See @nidoro and @feored's example [here](https://github.com/jakubfiala/atrament/issues/71#issuecomment-1214261577).\n\n## Development\n\nTo obtain the dependencies, `cd` into the atrament directory and run `npm install`.\nYou should be able to then build atrament by simply running `npm run build` and rebuild continuously with `npm run watch`.\n\n### Running the demo locally\n\nThe 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.\nA 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`.\n\n## Alternatives\n\nAtrament's philosophy is to provide a **simple** and **small** tool that takes care of everything from pointer events to drawing pixels on screen.\nAtrament 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)\nand pretty much as fast as the browser allows.\n\nThis does mean Atrament's rendering quality is limited by the Canvas API. If your application requires higher drawing quality, there are libraries such as\n[perfect-freehand](https://github.com/steveruizok/perfect-freehand) which compute their own curves and achieve somewhat more pleasing, higher-fidelity results.\nThis 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.).\n\nFor a more fully-featured solution including drawing shapes, graphs, text, built-in Undo/Redo and many other features,\nyou might want to consider a larger tool such as [excalidraw](https://github.com/excalidraw/excalidraw).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjakubfiala%2Fatrament","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjakubfiala%2Fatrament","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjakubfiala%2Fatrament/lists"}