Build an Infinite Canvas: A Step-by-Step Tutorial
Build an infinite canvas with JavaScript & TypeScript. Step-by-step guide with pan, zoom, touch support, and performance tips plus GitHub templates.

Short answer
An infinite canvas is a normal HTML canvas whose drawing coordinates are mapped to a virtual, unbounded coordinate system. You update a transform (translation + scale) on user events and redraw shapes with that transform. This tutorial shows a clear path: set up the canvas, map screen coordinates to world coordinates, handle pan and zoom (mouse, touch), and add performance tips so your zoomable canvas feels smooth.
What you need and why
- Basic JavaScript or TypeScript skills.
- Familiarity with the
canvas
API and event listeners. - Optional: React for component structure — see a practical guide like How to Create a Figma-like Infinite Canvas in React.
How does an infinite canvas work?
Think of the canvas like a window onto a huge map. Instead of moving everything on the map, you move the window and change its zoom. Internally you keep a simple state:
- offsetX, offsetY — where the window sits on the map (panning)
- scale — zoom level
To draw, convert world coordinates into screen coordinates using a transform matrix or by applying math before each draw. You can also use the Canvas 2D API transform calls like ctx.setTransform(scale, 0, 0, scale, tx, ty)
.
Step 1 — Initialize the canvas
Make the canvas match the viewport and set up a resize handler. Link: InfiniteCanvas explores advanced APIs but a simple setup works fine.
const canvas = document.querySelector("#c");
function resize() {
canvas.width = document.body.clientWidth;
canvas.height = document.body.clientHeight;
}
window.addEventListener("resize", resize);
resize();
Step 2 — Store view state
Keep a small state object. You’ll update this on user input and use it when drawing.
const view = {
offsetX: 0,
offsetY: 0,
scale: 1
};
Step 3 — Map coordinates
Convert mouse/touch positions (screen) to world coordinates for accurate drawing and interactions.
function screenToWorld(x, y) {
return {
x: (x - view.offsetX) / view.scale,
y: (y - view.offsetY) / view.scale
};
}
Step 4 — Handle panning
Panning changes offsetX/Y. On mouse drag, measure delta and add to offsets. Quick analogy: you’re sliding a magnifying glass over a poster. Quick check: if the mouse moves right by 10px and scale is 2, how much should offsetX change? (Answer: +10)
let isDragging = false;
let lastX = 0, lastY = 0;
canvas.addEventListener("mousedown", (e) => {
isDragging = true; lastX = e.clientX; lastY = e.clientY;
});
canvas.addEventListener("mousemove", (e) => {
if (!isDragging) return;
const dx = e.clientX - lastX;
const dy = e.clientY - lastY;
view.offsetX += dx;
view.offsetY += dy;
lastX = e.clientX; lastY = e.clientY;
requestAnimationFrame(draw);
});
canvas.addEventListener("mouseup", () => isDragging = false);
Step 5 — Handle zoom (mouse wheel)
Zoom around the mouse position. Convert the mouse position to world coordinates before and after changing scale, then adjust offsets so the point under the cursor stays in place.
canvas.addEventListener("wheel", (e) => {
e.preventDefault();
const mouse = screenToWorld(e.clientX, e.clientY);
const scaleFactor = Math.exp(-e.deltaY * 0.001);
const newScale = Math.max(0.1, Math.min(10, view.scale * scaleFactor));
view.offsetX = e.clientX - mouse.x * newScale;
view.offsetY = e.clientY - mouse.y * newScale;
view.scale = newScale;
requestAnimationFrame(draw);
});
Step 6 — Touch + pinch-to-zoom
Support two-finger pinch by tracking distance between touches. Many tutorials cover this; see a helpful explanation at Creating an infinite whiteboard and an implementation reference at Sandro Maglione's article.
function dist(a,b){
const dx=a.clientX-b.clientX, dy=a.clientY-b.clientY; return Math.hypot(dx,dy);
}
let prevDist = null;
canvas.addEventListener("touchmove", (e)=>{
if (e.touches.length===2){
const d = dist(e.touches[0], e.touches[1]);
if (prevDist){
const factor = d / prevDist;
// update view.scale and offsets similar to wheel handler
view.scale *= factor;
requestAnimationFrame(draw);
}
prevDist = d;
}
});
canvas.addEventListener("touchend", ()=> prevDist = null);
Step 7 — Draw using transform
Use ctx.setTransform
to apply scale and translate in one step, then draw world objects in their world coordinates.
function draw(){
const ctx = canvas.getContext("2d");
ctx.setTransform(view.scale,0,0,view.scale,view.offsetX,view.offsetY);
ctx.clearRect(-view.offsetX/view.scale, -view.offsetY/view.scale, canvas.width/view.scale, canvas.height/view.scale);
// draw grid or shapes in world space
ctx.fillStyle = "#333";
ctx.fillRect(0,0,100,100);
}
requestAnimationFrame(draw);
Performance tips
- Layer canvases: one for static content (grid), one for dynamic strokes.
- Cull off-screen objects before drawing.
- Throttle expensive work and use
requestAnimationFrame
. - Consider WebGL for thousands of objects; see infinitecanvas.cc or antv.vision for high-performance rendering approaches.
Where to go next
If you use React, check practical examples like Rob Pruzan's multiplayer infinite canvas and public templates on GitHub. For libraries, TiledCanvas and other projects offer trade-offs between control and convenience.
FAQ
Will this work on mobile?
Yes—handle touch events and pinch gestures. Test on real devices and tune gesture thresholds.
Should I use WebGL?
For simple apps, 2D canvas is fine. For many thousands of objects or vector editing, WebGL or a layered approach improves performance.
Can I make it multiplayer?
Yes. Send user actions (strokes, transforms) over WebSockets and replay them. See a multiplayer example at Rob Pruzan's post.
Final checklist
- Canvas resizes to viewport.
- Screen-to-world coordinate mapping implemented.
- Panning and zooming keep cursor focus.
- Touch gestures supported.
- Performance: layering, culling, rAF.
Want the full, commented template and a React component you can clone and run? Grab the repo examples linked earlier and try adding a simple sticky note layer. You’ll learn a lot by making one small change: add a button that drops a note at the screen center in world coordinates.
“Most production infinite canvases are just careful math and good rendering choices.” — practical advice from multiple community tutorials.