Why pointer events on SVG?
Pointer events unify mouse, touch, and stylus input behind a single API. On an SVG canvas that uses a viewBox, the browser maps the SVG viewport to a potentially different CSS pixel size — so raw clientX/clientY values are in screen pixels, not SVG user units. Feeding those directly into SVG coordinates produces mis-placed shapes whenever the canvas is resized. The fix is one matrix multiply:
getScreenCTM() returns the composite transform that maps SVG user units to screen pixels. Inverting it and multiplying the screen-space point through the inverse gives the correct SVG coordinate regardless of viewBox size, CSS zoom, or scroll position. This technique is covered in Tutorial 14: Scripting SVG with JavaScript.
Building shapes with createElementNS
SVG elements must be created with the SVG namespace URI — plain document.createElement('path') creates an HTML element that the layout engine does not render as SVG. The namespace-aware call is:
Freehand drawing begins a new <path> on pointerdown with an M (move-to) command, then appends L x y (line-to) commands on every pointermove. Rectangle, circle, and line modes keep a reference to the element created on pointerdown and update its geometry attributes on each pointermove, making the shape appear to stretch in real time.
Pointer event flow
The three handlers work together: pointerdown sets up state and creates the initial element; pointermove updates it while the pointer is held; pointerup finalises and clears the drawing state. setPointerCapture ensures pointermove events continue to fire even if the pointer leaves the SVG element — essential for shapes that span the full canvas.
Live demo
Select a shape and color, then click and drag on the canvas. The Clear button removes all drawn shapes. All controls are keyboard-accessible: press Tab to move between buttons, Enter or Space to activate.
Coordinate mapping in detail
The svgPoint function is the core of any pointer-driven SVG interaction. Here it is in full with annotations:
Without this step, drawing on a 600×340 viewBox displayed at 400px wide would place shapes at 1.5× the correct position along the x-axis. The matrix inversion handles any scaling, translation, or rotation applied to the SVG element automatically.
Accessibility considerations
The canvas itself carries role="img" and a descriptive aria-label so screen readers announce it as a single image rather than announcing each drawn path. All toolbar controls are native <button> elements — they receive focus, respond to Enter and Space, and carry aria-pressed to communicate the selected shape and color. The coordinate display uses aria-live="polite" so it does not interrupt ongoing narration.