JSFiddle - React, Tailwind, and code Playground

by Rob Louie

HTML

<canvas id="canvas" width="600" height="600"></canvas>

<div id="mouse-pos"></div>
<div id="transformed-mouse-pos"></div>

<div>
  Click and drag to move the image around within the canvas. Use your mouse wheel to zoom in and out at the position of your mouse cursor.
</div>

CSS

canvas {
  border: 1px solid gray;
}

JavaScript

// See blogpost here for more details: https://roblouie.com/article/617

const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');

// See individual pixels when zooming
context.imageSmoothingEnabled = false;


// Simply used to display the mouse position and transformed mouse position
const mousePos = document.getElementById('mouse-pos');
const transformedMousePos = document.getElementById('transformed-mouse-pos');

const image = new Image();
image.src = "https://roblouie.com/wp-content/uploads/2020/04/60788338_304920937106527_8424495022080625603_n.jpg";
image.onload = drawImageToCanvas;

let isDragging = false;
let dragStartPosition = { x: 0, y: 0 };
let currentTransformedCursor;

function drawImageToCanvas() {
	context.save();
  context.setTransform(1,0,0,1,0,0);
  context.clearRect(0,0,canvas.width,canvas.height);
  context.restore();

  context.drawImage(image, 0, 0, 200, 200);
}

function onMouseDown(event) {
	isDragging = true;
	dragStartPosition = getTransformedPoint(event.offsetX, event.offsetY);
}

function getTransformedPoint(x, y) {
	const originalPoint = new DOMPoint(x, y);
  return context.getTransform().invertSelf().transformPoint(originalPoint);
}

function onMouseMove(event) {
  currentTransformedCursor = getTransformedPoint(event.offsetX, event.offsetY)
  mousePos.innerText = `Original X: ${event.offsetX}, Y: ${event.offsetY}`;
  transformedMousePos.innerText = `Transformed X: ${currentTransformedCursor.x}, Y: ${currentTransformedCursor.y}`;
  
  if (isDragging) {

  	context.translate(currentTransformedCursor.x - dragStartPosition.x, currentTransformedCursor.y - dragStartPosition.y);
		drawImageToCanvas();
  }
}

function onMouseUp() {
	isDragging = false;
}

function onWheel(event) {
	const zoom = event.deltaY < 0 ? 1.1 : 0.9;
  
  context.translate(currentTransformedCursor.x, currentTransformedCursor.y);
  context.scale(zoom, zoom);
  context.translate(-currentTransformedCursor.x,...