To pan an SVG by dragging, translate the drawing in response to pointer movement. Use the root SVG’s viewBox when the whole SVG is the viewport; translate a scene group when elements such as controls or labels must stay fixed. Convert pointer positions from screen pixels to SVG user units, capture the pointer during the drag, and handle both completion and cancellation.
Choose what moves: the viewBox or a scene group
In the W3C SVG interaction specification, panning is a translation—a shift of an SVG document fragment in response to a user action (W3C SVG 1.1 interaction specification). The practical choice is which part of the document to shift.
| Approach | Use it when | What stays fixed | State and bounds |
|---|---|---|---|
Change the root SVG’s viewBox |
The SVG itself is the viewport and the whole drawing should move through it. | Nothing inside the SVG; the viewport element and external page content remain in place. | Store the viewBox origin and adjust it in SVG user units. Bounds are expressed as viewport coordinates. |
| Translate a scene group | Some SVG elements, such as an in-canvas toolbar or crosshair, must remain stationary. | Elements outside the translated group. | Store the group’s translation. Bounds are expressed in scene coordinates; zoom can share a transform on a wrapper group. |
Group translation is shown below. To use the viewBox model instead, update and persist its x and y origin during the drag; as the pointer moves right or down, move the origin left or up so the artwork appears to follow the pointer. Keep the same coordinate conversion and pointer lifecycle either way.
Build a drag interaction with Pointer Events
A dedicated transparent rectangle gives the user a predictable area to grab, including portions of the canvas where the artwork itself has no painted pixels. The SVG pointer-events property controls whether and when an element can be targeted by pointer input; values such as visiblePainted, all, and none produce different hit-testing behavior (MDN: SVG pointer-events).
Free tools Windows power users keep installed
One-click scans. No signup required.
#1 Best Overall
<svg id="viewport" viewBox="0 0 800 500" role="img" aria-label="Pannable illustration">
<rect class="pan-surface" x="0" y="0" width="800" height="500" fill="transparent"/>
<g id="scene">
<!-- Put movable artwork here. -->
</g>
</svg>
<script>
const svg = document.querySelector('#viewport');
const scene = document.querySelector('#scene');
const surface = svg.querySelector('.pan-surface');
let drag = null;
let translation = { x: 0, y: 0 };
function pointInSvg(e) {
const matrix = svg.getScreenCTM();
if (!matrix) return null;
return new DOMPoint(e.clientX, e.clientY)
.matrixTransform(matrix.inverse());
}
surface.addEventListener('pointerdown', (e) => {
if (drag) return;
const p = pointInSvg(e);
if (!p) return;
surface.setPointerCapture(e.pointerId);
drag = { id: e.pointerId, x: p.x, y: p.y,
startX: translation.x, startY: translation.y };
surface.style.cursor = 'grabbing';
});
surface.addEventListener('pointermove', (e) => {
if (!drag || e.pointerId !== drag.id) return;
const p = pointInSvg(e);
if (!p) return;
translation = {
x: drag.startX + p.x - drag.x,
y: drag.startY + p.y - drag.y
};
// Clamp translation here, then apply the bounded result.
scene.setAttribute('transform',
`translate(${translation.x} ${translation.y})`);
});
function endDrag(e) {
if (!drag || e.pointerId !== drag.id) return;
if (surface.hasPointerCapture(e.pointerId)) {
surface.releasePointerCapture(e.pointerId);
}
drag = null;
surface.style.cursor = 'grab';
}
surface.addEventListener('pointerup', endDrag);
surface.addEventListener('pointercancel', endDrag);
</script>
This example keeps the translation in JavaScript state so each pointer move is calculated from the position and translation at the start of the drag. If you only store translation in the SVG attribute and reinitialize it to zero on every drag, the drawing can jump back when the next drag begins. In a UI framework, keep this persistent value in component state.
Capture and finish the pointer correctly
Calling setPointerCapture(pointerId) on pointer down directs subsequent events for that pointer to the capture element, even after the pointer moves outside it. Listen for both pointerup and pointercancel; release capture when it is still held and clear the active drag. This makes the interaction resilient to leaving the hit surface and to input cancellation. The W3C pointer-event model describes how hit testing and captured events affect event targeting (W3C Pointer Events).
Rank #2
Let touch input reach the drag handler
Set a deliberate touch-action policy on the drag surface. For a canvas where dragging is the touch gesture, touch-action: none prevents the browser from taking over the gesture for page panning or zooming; use it narrowly, because it also removes those native gestures on that surface. If the page should retain a particular native gesture, choose a less restrictive policy that matches the design. A grab cursor before dragging and grabbing during it provide a useful mouse affordance.
Convert pointer pixels into SVG coordinates
clientX and clientY are screen-space CSS-pixel coordinates, while SVG geometry is measured in user units. Applying raw pixel deltas directly to a viewBox or SVG transform can make the drag feel wrong when the SVG is resized or its aspect ratio introduces scaling. Transform the pointer through the inverse of getScreenCTM(), as the example does, so the delta is measured in the SVG coordinate system. Check that getScreenCTM() returns a matrix before taking its inverse.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
The SVG API also documents currentTranslate and currentScale as state associated with user-agent magnification and panning (MDN: SVGSVGElement.currentTranslate). Those properties describe the SVG’s built-in user magnification and panning model; they are not a substitute for maintaining your own application’s viewBox or group-transform state.
Keep the drawing within useful bounds
Unbounded translation lets users drag all content out of sight. Clamp the proposed translation before applying it, using the viewport and content bounds in the same coordinate system. For a translated group, measure the scene’s bounds and constrain its translation so the desired amount of content remains visible. For a changing viewBox, constrain its origin against the permitted viewing region. Decide explicitly whether empty space is allowed at an edge; that choice determines whether content must cover the entire viewport or may stop short.
Rank #4
Make panning usable without dragging
Dragging alone can exclude users who cannot use a pointer. If panning is necessary to inspect or operate the SVG, provide keyboard controls or visible buttons to move the view in predictable increments. Route those controls through the same persistent translation or viewBox state and apply the same bounds, so pointer, keyboard, and programmatic changes stay consistent. Keep fixed controls outside the translated scene group so they do not move out of reach.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problems

