JSFiddle - React, Tailwind, and code Playground

HTML

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>

<canvas id="psd-canvas" width="1000" height="600"></canvas>

CSS

body{
  overflow: hidden;
}

JavaScript

var Zoompan = function(canvas){
    var self = this;

    var canvasPos = {
        deltaX: 0, 
        deltaY: 0
    };

    this.scale  = 1;
    this.enable = true;

    // our event object that handled clicking (mousedown), mousemove (dragging), mouseup (enddragging)
    var events = {
        dragging: false,
        mouseX: 0,
        mouseY: 0,
        cx: 0,
        cy: 0,

        mouseDown: function(e){
            if(!self.enable) return;

            // get the current mouse position (DRAGSTART)
            var r = canvas.getBoundingClientRect();

            events.mouseX = (e.clientX - r.left) - canvasPos.deltaX;
            events.mouseY = (e.clientY - r.top) - canvasPos.deltaY;

            events.cx = e.clientX;
            events.cy = e.clientY;

            events.dragging = true;
        },
        mouseMove: function(e){
            if (events.dragging && self.enable){   
                // get the current mouse position (updates every time the mouse is moved durring dragging)
                var r = canvas.getBoundingClientRect();
                var x = e.clientX - r.left;
                var y = e.clientY - r.top;

                // calculate how far we moved
                canvasPos.deltaX = (x - events.mouseX); // total distance in x
                canvasPos.deltaY = (y - events.mouseY); // total distance in y

                if(e.clientX + e.clientY == events.cx + events.cy) return;

                // we need to clear the canvas, otherwise we'll have a bunch of overlapping images
                //ctx.clearRect(0,0, canvas.width, canvas.height);

                self.dispatchEvent({type: 'draw', x: canvasPos.deltaX, y: canvasPos.deltaY, scale: self.scale});
                self.dispatchEvent({type: 'move', x: canvasPos.deltaX, y: canvasPos.deltaY, scale: self.scale});
            }
        },
        mouseUp: function (e) {
            events.dragging = false;
        },
        mouseWheel: function (e) {
           ...