Vale Steve Vale Steve - 4 months ago 14
Javascript Question

Canvas Coordinate after scaling/Panning

I'm building a Canvas by some data that i receive from api, and this is all fine. Is a few days anyway that I'm stucked on the fact that with the following code `canvas.addEventListener('wheel', (event: MouseWheelEvent) => {
event.preventDefault();

let coords = Positioning.transformedPoint(
event.pageX - canvas.offsetLeft,
event.pageY - canvas.offsetTop
);

canvasMethods.clear();
canvasMethods.translate(coords.x, coords.y);

if (event.wheelDeltaY > 0) {
canvasMethods.scale(ZoomDirection.ZOOM_IN);
} else if (event.wheelDeltaY < 0) {
canvasMethods.scale(ZoomDirection.ZOOM_OUT);
}

canvasMethods.translate(-coords.x, -coords.y);

this._renderFn();
}, false);

canvas.addEventListener('mousedown', (event: MouseEvent) => {
event.preventDefault();

this._dragging = true;
this._dragStart = Positioning.transformedPoint(
event.clientX - canvas.offsetLeft,
event.clientY - canvas.offsetTop
);
}, false);

canvas.addEventListener('dblclick', (event: MouseEvent) => {
let coords = Positioning.transformedPoint(
event.clientX - canvas.offsetLeft,
event.clientY - canvas.offsetTop
);
this._clickFn(coords);
});

canvas.addEventListener('mousemove', (event: MouseEvent) => {
if (this._dragging) {
event.preventDefault();

this._dragEnd = Positioning.transformedPoint(
event.pageX - canvas.offsetLeft,
event.pageY - canvas.offsetTop
);

let coords = Positioning.transformedPoint(
event.clientX - canvas.offsetLeft,
event.clientY - canvas.offsetTop
);

canvasMethods.translate(coords.x - this._dragStart.x, coords.y - this._dragStart.y);
canvasMethods.clear();

this._renderFn();
this._dragStart = this._dragEnd;
}
}, false);

canvas.addEventListener('mouseup', (event: MouseEvent) => {
event.preventDefault();

this._dragging = false;
this._dragStart = null;
this._dragEnd = null;
})
}`


i get the right coordinates when it's on normale scale, but as soon as i scale i get an incremental error( basically the distance between the actual point and the mouse cursor is getting bigger and bigger ) and i cannot figure why. For calculate the coord in a Matrix i use the SVG method as wrapper in the following way ` export class Positioning {
static svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
private static xform = Positioning.svg.createSVGMatrix();

static transformedPoint(x: number, y: number): SVGPoint {
let coords = Positioning.svg.createSVGPoint();

coords.x = x;
coords.y = y;

return coords.matrixTransform(Positioning.xform.inverse());
}


}`

I know that this somehow has to do with caling but i really cannot figure how get account of the scaling and do the proper operation to obtain the proportion. I've also checked this answer Zoom Canvas to Mouse Cursor that's pretty accurate but actually he get account of it in some way that i cannot reconize. Have someone else faced the same problem?

Answer

I've solved the issue, i was forgetting an important stuff, to track my transformation. Now i'll link the code to be useful for who maybe can encounter my same issue ( if you are not using Typescript can avoid the typings ofc)

Basically I'm using the SVG Matrix methods to track down what is happening in the canvas.

This is the class that get account of Canvas transformation by storing in itself the basic Matrix of a untransformed canvas(1,0,0,1,0,0) created by createSVGMatrix() and tracking the transformation through native SVGMatrix methods that are updating the orginal matrix. Then when i need i just use the transformed point method to have back the proper x,y coord of the mouse back scaled proportionally to the matrix.

export class Positioning {
static svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
static MatrixTransformationProxy = Positioning.svg.createSVGMatrix();

static transformedPoint(x: number, y: number): SVGPoint {
  let coords = Positioning.svg.createSVGPoint();

  coords.x = x;
  coords.y = y;

  return coords.matrixTransform(Positioning.MatrixTransformationProxy.inverse());
}

static translateProxy(x: number, y: number) {
  Positioning.MatrixTransformationProxy = Positioning.MatrixTransformationProxy.translate(x, y);
}

static scaleUniformProxy(scaleValue: number) {
  Positioning.MatrixTransformationProxy = Positioning.MatrixTransformationProxy.scale(scaleValue);
}

}

I'm keeping in Sync my canvas transformation with my ProxyMatrix using wrapper for canvas methods that invoke the Canvas method and the relative Positioning method to update the MatrixTransformationProxy like this

export class CanvasMethods {
static getSharedInstance(): CanvasMethods {
  return sharedInstance;
}

private _canvas: HTMLCanvasElement;

getCanvas(): HTMLCanvasElement {
  return this._canvas;
}

getContext(): CanvasRenderingContext2D {
  return this._canvas.getContext('2d');
}

registerCanvas(canvas: HTMLCanvasElement): void {
  this._canvas = canvas;
}

getCanvasBoundingRect(): BoundingRect {
  return new BoundingRect(0, 0, this._canvas.width, this._canvas.height);
}

clear(): void {
  this.getContext().save();
  this.getContext().setTransform(1, 0, 0, 1, 0, 0);
  this.getContext().clearRect(0, 0, this._canvas.width, this._canvas.height);
  this.getContext().restore();
}

scale(direction: ZoomDirection): void {
  let options = { //TODO get this from constructor options
    scaleValueOut: 0.8,
    scaleValueIn: 1.1
  };

  if (direction === ZoomDirection.ZOOM_OUT) {
    Positioning.scaleUniformProxy(options.scaleValueOut);
    this.getContext().scale(options.scaleValueOut, options.scaleValueOut);
  } else if (direction === ZoomDirection.ZOOM_IN) {
    Positioning.scaleUniformProxy(options.scaleValueIn);
    this.getContext().scale(options.scaleValueIn, options.scaleValueIn);
  }
}

translate(x: number, y: number): void {
  Positioning.translateProxy(x, y);
  this.getContext().translate(x, y);
}

}

const sharedInstance = new CanvasMethods(); }