Module js-draw - v1.25.0

The main entrypoint for the NPM package. Everything exported by this file is available through the js-draw package.

import { Editor, Vec3, Mat33, EditorSettings, ShortcutManager } from 'js-draw';

// Use the Material Icon pack.
import { MaterialIconProvider } from '@js-draw/material-icons';

// Apply js-draw CSS
import 'js-draw/styles';
// If your bundler doesn't support the above, try
// import 'js-draw/bundledStyles';

(async () => {
	// All settings are optional! Try commenting them out.
	const settings: EditorSettings = {
		// Use a non-default set of icons
		iconProvider: new MaterialIconProvider(),

		// Only capture mouse wheel events if the editor has focus. This is useful
		// when the editor is part of a larger, scrolling page.
		wheelEventsEnabled: 'only-if-focused',
	};

	// Create the editor!
	const editor = new Editor(document.body, settings);

	// Adds the defualt toolbar
	const toolbar = editor.addToolbar();

	// Increases the minimum height of the editor
	editor.getRootElement().style.height = '600px';

	// Loads from SVG data
	await editor.loadFromSVG(`
		<svg
			viewBox="0 0 500 500"
			width="500" height="500"
			version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg"
		>
			<path
				d="M500,500L500,0L0,0L0,500L500,500"
				fill="#aaa"
				class="js-draw-image-background"
			/>
			<text
				style="transform: matrix(1, 0, 0, 1, 57, 192); font-family: serif; font-size: 32px; fill: #111;"
			>Testing...</text>
		</svg>
	`);

	// Adding tags to a toolbar button allows different styles to be applied.
	// Also see addActionButton.
	toolbar.addSaveButton(() => {
		const saveData = editor.toSVG().outerHTML;

		// Do something with saveData
		alert('Saved data!\n\n' + saveData);
	});

	toolbar.addExitButton(() => {
		// Save/confirm exiting here?
		editor.remove();
	});
})();
js-draw logo

js-draw

NPM package | GitHub | Documentation | Try it!

For example usage, see one of the examples or read the documentation.

Features

A core feature of js-draw is its large zoom range (from roughly 10⁻¹⁰x to 10¹⁰x).

Demo

Applications using js-draw can adjust this zoom range with custom EditorSettings.

js-draw supports touchscreen pinch zoom and rotate gestures. To simplify editing, screen rotation snaps to multiples of 90 degrees.

Demo

It's also possible to disable touch drawing. This can be useful when drawing with a stylus and can be done with either PanZoomTool.setMode or, by a user, with the "hand" tool menu:

screenshot: Hand tool menu at the bottom of the screen includes 'lock rotation' and 'touchscreen panning'. 'Touchscreen panning' is highlighted.

With the default toolbar, users can change the pen style, color, and more:

screenshot: Pen configuration menu includes pen size, color, type, stabilization, autocorrect,...

It's possible for applications using js-draw to add custom pen types that can also be customized in this way. It's also possible to save the toolbar state and restore it after reloading the app.

js-draw also supports:

API

To create a new Editor and add it as a child of document.body, use the Editor constructor:

import Editor from 'js-draw';
import 'js-draw/styles';

const editor = new Editor(document.body);

The import js-draw/styles step requires a bundler that can import .css files. For example, webpack with css-loader.

Import the pre-bundled version of the editor to apply CSS after loading the page.

import Editor from 'js-draw';
import 'js-draw/bundledStyles';

const editor = new Editor(document.body);

js-draw/bundledStyles is a version of the editor's stylesheets pre-processed by Webpack. As such, importing or including it with a <script src="..."></script> tag applies editor-specific CSS to the document.

If you're not using a bundler, consider using the pre-bundled editor:

<!-- Replace 1.0.0 with the latest version of js-draw -->
<script src="https://cdn.jsdelivr.net/npm/js-draw@1.0.0/dist/bundle.js"></script>
<script>
const editor = new jsdraw.Editor(document.body);
editor.addToolbar();
editor.getRootElement().style.height = '600px';
</script>

Note: To ensure the CDN-hosted version of js-draw hasn't been tampered with, consider including an integrity="..." attribute. Read more about using SRI with JSDelivr.

To create a toolbar with buttons for the default tools:

const toolbar = editor.addToolbar();

Save and exit buttons can be added with the .addSaveButton and .addExitButton methods:

toolbar.addSaveButton(() => {
const svgElem = editor.toSVG();
console.log('The saved SVG:', svgElem.outerHTML);
});

toolbar.addExitButton(() => {
// Save here?

// Removes the editor from the document.
editor.remove();
});

Custom actions can also be added to the toolbar. For example,

toolbar.addActionButton('Custom', () => {
// When the action button is pressed
});

or alternatively, with an icon,

toolbar.addActionButton(
{
label: 'Custom',
icon: editor.icons.makeSaveIcon(),
},
() => {
// Do something here
},
);
editor.loadFromSVG(`
<svg
viewBox="156 74 200 150"
width="200" height="150"
>
<path d="M156,150Q190,190 209,217L213,215Q193,187 160,148M209,217Q212,218 236,178L232,176Q210,215 213,215M236,178Q240,171 307,95L305,93Q237,168 232,176M307,95Q312,90 329,78L327,74Q309,87 305,93" fill="#07a837"></path>
</svg>
`);

Note: While js-draw supports only a small subset of the SVG markup language, it tries to preserve unrecognised SVG elements.

For example, although js-draw doesn't support <circle/> elements,


    
    

renders as

screenshot of the image editor, displaying a green checkmark. The circle is invisible

but exports to


which does contain the <circle/> element.

The background color and style can be customized with editor.setBackgroundStyle. For example,

import { Editor, Color4, BackgroundComponentBackgroundType } from 'js-draw';
const editor = new Editor(document.body);

editor.dispatch(
editor.setBackgroundStyle({
color: Color4.orange,
type: BackgroundComponentBackgroundType.Grid,
}),
);

Above, we use editor.dispatch because setBackgroundStyle returns a Command, rather than changing the background style directly. js-draw uses Commands to track actions that can be undone and redone.

By default, .dispatch adds Commands to the undo stack. To avoid this, pass false for the second parameter to .dispatch:

const addToHistory = false;
editor.dispatch(
editor.setBackgroundStyle({
color: Color4.orange,
type: BackgroundComponentBackgroundType.Grid,
}),
addToHistory,
);

By default, the background has a fixed size and marks the region that will be saved by .toSVG or .toDataURL. It's possible to make the background auto-resize to the content of the image with editor.image.setAutoresizeEnabled(true):

const editor = new Editor(document.body);

const addToHistory = false;
editor.dispatch(editor.image.setAutoresizeEnabled(true), addToHistory);

// Alternatively, using .setBackgroundStyle:
editor.dispatch(editor.setBackgroundStyle({ autoresize: true }), addToHistory);

To save as an SVG, use editor.toSVG(), which returns an HTMLSVGElement. Alternatively, if working with very large images that need to be saved in the background, consider using editor.toSVGAsync().

It's also possible to render the editor to a PNG or JPEG data URL. This can be done with editor.toDataURL().

The region of the image that will be saved can be changed by calling editor.image.setImportExportRect or

Touchpad/mousewheel pan gestures can conflict with gestures used to scroll the document. To turn off touchpad pan gestures (and scrolling the editor with the mousewheel),

const editor = new Editor(document.body, {
wheelEventsEnabled: false,
});

Alternatively, to only enable touchpad panning when the editor has focus,

const editor = new Editor(document.body, {
wheelEventsEnabled: 'only-if-focused',
});

If a user's language is available in src/localizations/ (as determined by navigator.languages), that localization will be used.

To override the default language, use getLocalizationTable([ 'custom locale here' ]). For example,

const editor = new Editor(document.body, {
// Force the Spanish (Español) localizaiton
localization: getLocalizationTable(['es']),
});
Creating a custom localization

See src/localization.ts for a list of strings that can be translated.

Many of the default strings in the editor might be overridden like this:

const editor = new Editor(document.body, {
// Example partial Spanish localization
localization: {
// Not all translated strings need to be specified. If a string isn't given,
// the English (default) localization will be used

// Strings for the main editor interface
// (see packages/js-draw/src/localization.ts)
loading: (percentage: number) => `Cargando: ${percentage}%...`,
imageEditor: 'Editor de dibujos',

undoAnnouncement: (commandDescription: string) => `${commandDescription} fue deshecho`,
redoAnnouncement: (commandDescription: string) => `${commandDescription} fue rehecho`,

// Strings for the toolbar
// (see src/toolbar/localization.ts)
pen: 'Lapiz',
eraser: 'Borrador',
select: 'Selecciona',
thicknessLabel: 'Tamaño: ',
colorLabel: 'Color',

...
},
});

By default, the editor's minimum and maximum zoom are very large (2·10-10x and 1012x, respectively). These are configurable by the minZoom and maxZoom settings. For example,

const editor = new Editor(document.body, {
minZoom: 0.5,
maxZoom: 2,
});

The editor's color theme is specified using CSS. Its default theme looks like this:

.imageEditorContainer {
/* Deafult colors for the editor -- light mode */

/* Used for unselected buttons and dialog text. */
--background-color-1: white;
--foreground-color-1: black;

/* Used for some menu/toolbar backgrounds. */
--background-color-2: #f5f5f5;
--foreground-color-2: #2c303a;

/* Used for other menu/toolbar backgrounds. */
--background-color-3: #e5e5e5;
--foreground-color-3: #1c202a;

/* Used for selected buttons. */
--selection-background-color: #cbdaf1;
--selection-foreground-color: #2c303a;

/* Used for dialog backgrounds */
--background-color-transparent: rgba(105, 100, 100, 0.5);

/* Used for shadows */
--shadow-color: rgba(0, 0, 0, 0.5);

/* Color used for some button/input foregrounds */
--primary-action-foreground-color: #15b;
}

@media (prefers-color-scheme: dark) {
.imageEditorContainer {
/* Default colors for the editor -- dark mode */
--background-color-1: #151515;
--foreground-color-1: white;

--background-color-2: #222;
--foreground-color-2: #efefef;

--background-color-3: #272627;
--foreground-color-3: #eee;

--selection-background-color: #607;
--selection-foreground-color: white;
--shadow-color: rgba(250, 250, 250, 0.5);
--background-color-transparent: rgba(50, 50, 50, 0.5);

--primary-action-foreground-color: #7ae;
}
}

To override it, use a more specific CSS selector to set the theme variables. For example,

/* Notice the "body" below -- the selector needs to be more specific than what's in js-draw */
body .imageEditorContainer {
--background-color-1: green;
--foreground-color-1: black;

/* For this theme, use the same secondary and tertiary colors
(it's okay for them to be the same). */
--background-color-2: lime;
--foreground-color-2: black;
--background-color-3: lime;
--foreground-color-3: black;

--background-color-transparent: rgba(255, 240, 200, 0.5);
--shadow-color: rgba(0, 0, 0, 0.5);

--selection-background-color: yellow;
--selection-foreground-color: black;
}

disables the dark theme and creates a theme that primarily uses yellow/green colors.

See also adjustEditorThemeForContrast.

Examples and resources

Enumerations

BackgroundComponentBackgroundType
ComponentSizingMode
EditorEventType
EraserMode
InputEvtType
PanZoomMode
PointerDevice
RenderingMode
ToolbarWidgetTag
UndoEventType

Classes

AbstractComponent
AbstractRenderer
AbstractToolbar
ActionButtonWidget
BackgroundComponent
BaseTool
BaseToolWidget
BaseWidget
CanvasRenderer
Command
Display
DocumentPropertiesWidget
DummyRenderer
Duplicate
Editor
EditorImage

Handles lookup/storage of elements in the image.

js-draw images are made up of a collection of AbstractComponents (which includes Strokes, TextComponents, etc.). An EditorImage is the data structure that stores these components.

Here's how to do a few common operations:

Example:

import { Editor, RenderingStyle, Erase, Stroke, pathToRenderable } from 'js-draw';
import { Path, Color4, Point2, Vec2, Rect2 } from '@js-draw/math';
const editor = new Editor(document.body);


// //////////////// //
// Helper functions //
// //////////////// //

function addStroke(path: Path, style: RenderingStyle) {
	const stroke = new Stroke([
		pathToRenderable(path, style)
	]);

	// Create a command that adds the stroke to the image
	// (but don't apply it yet).
	const command = editor.image.addElement(stroke);
	// Actually apply the command.
	editor.dispatch(command);
}

function addBoxAt(point: Point2, color: Color4) {
	// Create a 10x10 square at the given point:
	const box = new Rect2(point.x, point.y, 10, 10);

	addStroke(
		Path.fromRect(box),
		{ fill: color },
	);
}


function makeTrashIcon() {
	const container = document.createElement('div');
	container.textContent = '🗑️';
	return container;
}


// //////////////////// //
// End helper functions //
// //////////////////// //



// Add some components to the image:
addBoxAt(Vec2.of(0, 0), Color4.green);
addBoxAt(Vec2.of(20, 0), Color4.orange);
addBoxAt(Vec2.of(20, 20), Color4.blue);

// Get the components in a small rectangle near (0, 0)
const components = editor.image.getElementsIntersectingRegion(
	new Rect2(0, 0, 5, 5), // a 5x5 square with top left (0, 0)
);


// Add a button that removes the components
const toolbar = editor.addToolbar();
toolbar.addActionButton({
	icon: makeTrashIcon(),
	label: 'remove near (0,0)',
}, () => {
	const deleteCommand = new Erase(components);
	editor.dispatch(deleteCommand);
});

Erase
EraserTool
EraserToolWidget
EventDispatcher
HandToolWidget
IconProvider
ImageComponent
InputMapper
InsertImageWidget
KeyBinding
KeyboardShortcutManager
MutableReactiveValue
PanZoomTool
PasteHandler
PenTool
PenToolWidget
Pointer
ReactiveValue
SelectAllShortcutHandler
SelectionTool
SelectionToolWidget
SerializableCommand
SoundUITool
Stroke
StrokeSmoother
SVGLoader
SVGRenderer
TextComponent
TextTool
TextToolWidget
ToolbarShortcutHandler
ToolController
ToolEnabledGroup
ToolSwitcherShortcut
UndoRedoHistory
UndoRedoShortcut
Viewport

Interfaces

ColorPickerColorSelected
ColorPickerToggled
CommandDoneEvent
CommandUndoneEvent
ComponentBuilder
ContextMenuEvt
CopyEvent
DisplayResizedEvent
EditorLocalization
EditorObjectEvent
EditorSettings
EditorToolEvent
EditorUndoStackUpdated
EditorViewportChangedEvent
GestureCancelEvt
ImageLoader
KeyPressEvent
KeyUpEvent
PasteEvent
PenStyle
PointerDownEvt
PointerEvtListener
PointerMoveEvt
PointerUpEvt
ReadOnlyToggled
RenderablePathSpec
RenderingStyle
RestyleableComponent
RestyleableComponentStyle
SelectionUpdated
StrokeDataPoint
StrokeSmootherCurve
TextRenderingStyle
ToolbarDropdownShownEvent
WheelEvt

Type Aliases

ComponentAddedListener
ComponentBuilderFactory
DeserializeCallback
EditorEventDataType
EditorNotifier
HTMLPointerEventFilter
HTMLPointerEventName
IconElemType
InputEvt
LoadSaveData
LoadSaveDataTable
OnDetermineExportRectListener
OnProgressListener
PointerEvt
PointerEvtType

Variables

defaultEditorLocalization

Functions

adjustEditorThemeForContrast
createRestyleComponentCommand
getLocalizationTable
invertCommand
isPointerEvt
isRestylableComponent
keyPressEventFromHTMLEvent
keyUpEventFromHTMLEvent
makeArrowBuilder
makeColorInput
makeDropdownToolbar
makeEdgeToolbar
makeFilledRectangleBuilder
makeFreehandLineBuilder
makeLineBuilder
makeOutlinedCircleBuilder
makeOutlinedRectangleBuilder
makePolylineBuilder
makePressureSensitiveFreehandLineBuilder
matchingLocalizationTable
pathFromRenderable
pathToRenderable
pathVisualEquivalent
sendPenEvent
sendTouchEvent
uniteCommands

References

default → Editor
HTMLToolbar → AbstractToolbar
StrokeComponent → Stroke
Text → TextComponent
OpenSource licenses