Canvas Editor: Developing Drawing and Annotation Tools

Once a startup came to us wanting to embed PDF annotation into a web application. They used an SVG overlay and experienced lag on a 200-page document — every stroke took a second to respond. We migrated them to Canvas with retained mode (Fabric.js) — and rendering speed increased by 8x, and users st

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1285
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1241
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    982
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    1033
  • image_website-sbh_0.webp
    Website development for SBH Partners
    1104
  • image_website-_0.webp
    Website development for Red Pear
    554

Once a startup came to us wanting to embed PDF annotation into a web application. They used an SVG overlay and experienced lag on a 200-page document — every stroke took a second to respond. We migrated them to Canvas with retained mode (Fabric.js) — and rendering speed increased by 8x, and users stopped complaining about lag. Over 5+ years, we have implemented over 50 canvas tools: from simple brushes to full-fledged diagram editors.

Regardless of the scenario — freehand drawing, document annotation, or diagrams — the basic architecture boils down to choosing between immediate mode and retained mode. Each approach has its trade-offs, and we will help you choose the right one for your tasks.

Technical Challenges in Developing Canvas Tools

Freehand drawing with pressure sensitivity. Not all browsers correctly transmit pen pressure — we have to emulate line width through pen speed. In 80% of cases, the problem is solved in a day: add a polyfill for Pointer Events and calibrate the algorithm for specific devices (Windows Ink, iPad, Wacom).

PDF annotation. Annotation coordinates must be tied to the page, not the screen. When zooming and scrolling, positions are recalculated with respect to the current scale. We use scalable layers and intercept transform events in Konva.js.

Export validation. Annotations must precisely overlay the original. We first render the PDF as an image, then overlay the canvas with annotations — this eliminates DPI discrepancies.

Why Retained Mode is Preferable for Annotations?

Immediate mode (pure Canvas 2D) is fast but requires manual hit-testing and scene management. Retained mode (Fabric.js, Konva.js) provides an object model: each shape is an object that can be selected, moved, or modified. For editors with undo/redo and export, retained mode cuts development time in half. Additionally, retained mode is more convenient for teamwork: changes in the object model are easily synchronized over WebSocket — enabling online collaborative drawing.

Characteristic Immediate mode Retained mode
Performance High (low overhead) Medium (depends on object count)
Object model None, written manually Built-in (shapes, groups)
Undo/Redo Bulky (pixel strings) Simple (object snapshots)
Hit-testing Coordinate math Built-in (based on bounding boxes)
Suitable for Freehand, brushes, elastic lines Shapes, text, object selection

How We Implement Annotation with Konva.js

We use Konva.js 9 with React-Konva. Each annotation is an object of class Rect, Line, Circle, Text, or Arrow. The user selects a tool and draws on the stage — the object is added to an array and rendered. Selection, moving, and editing are built-in.

import { Stage, Layer, Line, Rect, Circle, Text, Transformer } from 'react-konva'; import Konva from 'konva'; type AnnotationType = 'line' | 'rect' | 'circle' | 'arrow' | 'text'; interface Annotation { id: string; type: AnnotationType; points?: number[]; x?: number; y?: number; width?: number; height?: number; text?: string; color: string; } function AnnotationTool({ backgroundImage }: { backgroundImage: string }) { const [annotations, setAnnotations] = useState<Annotation[]>([]); const [selectedId, setSelectedId] = useState<string | null>(null); const [activeTool, setActiveTool] = useState<AnnotationType>('rect'); const [isDrawing, setIsDrawing] = useState(false); const stageRef = useRef<Konva.Stage>(null); function getRelativePosition() { const stage = stageRef.current!; const pos = stage.getPointerPosition()!; return { x: pos.x, y: pos.y }; } function handleMouseDown() { setSelectedId(null); const pos = getRelativePosition(); const newAnnotation: Annotation = { id: crypto.randomUUID(), type: activeTool, color: '#ef4444', x: pos.x, y: pos.y, width: 0, height: 0, }; if (activeTool === 'line') { newAnnotation.points = [pos.x, pos.y, pos.x, pos.y]; } setAnnotations((prev) => [...prev, newAnnotation]); setIsDrawing(true); } function handleMouseMove() { if (!isDrawing) return; const pos = getRelativePosition(); const lastIndex = annotations.length - 1; const last = annotations[lastIndex]; const updated = { ...last }; if (activeTool === 'line') { updated.points = [last.points![0], last.points![1], pos.x, pos.y]; } else { updated.width = pos.x - last.x!; updated.height = pos.y - last.y!; } setAnnotations((prev) => [...prev.slice(0, lastIndex), updated]); } function handleMouseUp() { setIsDrawing(false); } return ( <Stage ref={stageRef} width={800} height={600} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} > <Layer> {annotations.map((ann) => { if (ann.type === 'rect') { return ( <Rect key={ann.id} x={ann.x} y={ann.y} width={ann.width} height={ann.height} stroke={ann.color} strokeWidth={2} fill="transparent" onClick={() => setSelectedId(ann.id)} draggable={selectedId === ann.id} /> ); } if (ann.type === 'line') { return ( <Line key={ann.id} points={ann.points} stroke={ann.color} strokeWidth={2} lineCap="round" /> ); } return null; })} </Layer> </Stage> ); } 

How to Implement Undo/Redo in a Canvas Editor?

For undo and redo we use a state stack. Each operation creates a snapshot of the current annotations and places it in history. Branching is supported — if a new action is taken after an undo, the history is truncated.

function useUndoRedo<T>(initialState: T) { const [history, setHistory] = useState<T[]>([initialState]); const [cursor, setCursor] = useState(0); const current = history[cursor]; function push(newState: T) { // Truncate history after current position (branching) const newHistory = [...history.slice(0, cursor + 1), newState]; setHistory(newHistory); setCursor(newHistory.length - 1); } function undo() { if (cursor > 0) setCursor((c) => c - 1); } function redo() { if (cursor < history.length - 1) setCursor((c) => c + 1); } return { current, push, undo, redo, canUndo: cursor > 0, canRedo: cursor < history.length - 1 }; } 

Canvas Tool Development Process

Stage Description Timeline
Analysis Study scenarios: freehand, annotation, diagrams. Determine serialization format (JSON, SVG). 1 day
Design Choose stack (Canvas 2D, Konva, Fabric). Design object model and layer structure. 1-2 days
Implementation Develop tools (brush, shapes, text). Integrate undo/redo, export, pointer support. 3-5 days
Testing Test on different devices (iPad, Windows, Android). Fix bugs with touch events and pressure. 1-2 days
Deployment Document API, configure build, train client's team. 1 day

How We Export the Annotated Image

The background (PDF or image) is rendered as the first layer, annotations as the second. Then the entire stage canvas is converted to Blob via toBlob(). HiDPI is considered: multiply dimensions by window.devicePixelRatio.

function exportAnnotated(stage: Konva.Stage, bgImage: HTMLImageElement) { const canvas = document.createElement('canvas'); canvas.width = stage.width(); canvas.height = stage.height(); const ctx = canvas.getContext('2d')!; // First the background ctx.drawImage(bgImage, 0, 0); // Overlay annotations from Konva const stageCanvas = stage.toCanvas(); ctx.drawImage(stageCanvas, 0, 0); canvas.toBlob((blob) => { const url = URL.createObjectURL(blob!); const a = document.createElement('a'); a.href = url; a.download = 'annotated.png'; a.click(); URL.revokeObjectURL(url); }); } 
Checklist of Common Mistakes in Canvas Tool Development
  • touchAction: none not set — touch events are intercepted by the browser.
  • Coordinates not scaled on window resize — use resize handler with scale recalculation.
  • Undo/Redo stores full states (full snapshots) — for large documents memory can grow; in production use Command pattern with closures.
  • Export to PNG without considering devicePixelRatio — image is blurry on Retina.
  • pointercancel event not handled — gesture interruption leaves an artifact.

Timeline and What's Included

Timeline: basic tool with brush and shapes — from 3 to 5 days. PDF annotation with coordinate preservation — from 7 to 10 days. Cost is calculated individually after analyzing your technical specification. Thanks to architecture optimization, you can save up to 40% of development time.

What's included:

  • Source code with comments
  • API documentation (main methods and events)
  • Deployment instructions
  • 2 hours of training for the client's team
  • 30-day warranty on bug fixes

Why choose us: 5+ years of experience in canvas solutions development, 50+ implemented projects, certified React and Node.js developers. Contact us for a consultation on Canvas architecture — we'll respond within a day.

For reference: Canvas API is part of the WHATWG HTML Living Standard, retained mode is a graphics programming paradigm.