Javascript Cursor Zooming

511 views Asked by At

Is there a way to zoom on the cursor point using this code? I can't get my head around to doing it. The canvas zooms but it only zooms in and out from the top left corner.

var previousMousePosition = new Vector(0, 0);
function OnMouseWheel (event) {
    var delta = event.wheelDelta ? event.wheelDelta/40 : event.detail ? -event.detail : 0;
    var mousePosition = new Vector(event.clientX, event.clientY);
    var scaleFactor = 1.1;  
    if (delta) {
        var factor = Math.pow(scaleFactor,delta);
        context.scale(factor,factor);
    }
}
2

There are 2 answers

0
markE On

The canvas always scales from it current origin. The default origin is [0,0].

If you want to scale from another point, you can first do context.translate(desiredX,desiredY);. This will reset the origin of the canvas to [desiredX,desiredY].

That way your context.scale will scale from your specified origin.

Since all context transformations remain in effect for every subsequent drawing, you often want to reverse the transformations after you are done with the current drawing (=='resetting' for the next drawing which might/might not use the current transformations). To untransform, just call the transformation with negative arguments: eg. context.scale(-factor,-factor). Transformations should be done in reverse order from their original transformations.

So your refactored code could be:

// set the origin to mouse x,y
context.translate(mousePosition.x,mousePosition.y);

// scale the canvas at x,y
context.scale(factor,factor);

// ...draw stuff

// reverse the previous scale
context.scale(-factor,-factor);

// reverse the previous translate
context.translate(-mousePosition.x,-mousePosition.y);
0
RickyTomatoes On

First, I'd like to point out that from your code it looks like you're listening for 'mousewheel' events on the canvas. As noted here, the 'mousewheel' event is non-standard and is not on track to become a standard. As a result, you'll get results that are, at best, mixed when listening for it. The 'scroll' event is available on nearly every platform, and will likely be a better avenue for capturing user input.

As far as your question, you're on the right track for the behavior that you're looking for, but you're missing one step.

When you call scale on a canvas context object, the behavior is very simple. Starting at the top left corner (0,0), the method scales the points of the canvas by the factors provided. Say you have a 10x10 canvas, and a black dot at 1,1. If the canvas is scaled by a factor of 2 on both axes, the 0,0 point will stay in the same place but point 1,1 will be where the point 2,2 was before scaling.

In order to achieve the 'zooming' behavior you're looking for, the context has to be translated after scaling so that the reference point occupies the same physical position it did before the scaling. In your case, the reference point is the point where the user's cursor sits when the zoom action is performed.

Luckily, the canvas context object provides a translate(x,y) method that moves the origin of the context relative to the 0,0 point of the canvas. To translate it the correct ammount, you have to:

  1. Calculate the distance of the mouse cursor from the canvas origin before zooming
  2. Divide that distance by the scaling factor
  3. Translate the origin by that value

Since your code doesn't indicate the structure of your HTML, below I've marked it up with some comments and pseudocode to show how you might implement this algorithm:

//You'll need to get a reference to your canvas in order to calculate the relative position of
//the cursor to its top-left corner, and save it to a variable that is in scope inside of your
//event handler function

var canvas = document.getElementById('id_of_your_canvas');

//We're also going to set up a little helper function for returning an object indicating
//the position of the top left corner of your canvas element (or any other element)

function getElementOrigin(el){
    var boundingBox = el.getBoundingClientRect();
    return { x: boundingBox.left, y: boundingbox.top};
}

function OnMouseWheel (event) {
    //you probably want to prevent scrolling from happening or from bubbling so:
    event.preventDefault();
    event.stopPropagation();

    var delta = event.wheelDelta ? event.wheelDelta/40 : event.detail ? -event.detail : 0;

    var canvasCorner  = getElementOrigin(canvas)
    //JavaScript doesn't offer a 'vector' or 'point' class natively but we don't need them 
    var mousePosition = {x: event.clientX, y: event.clientY};
    var diff = {x: mousePostion.x - canvasCorner.x, y: mousePosition.y - canvasCorner.y};
    var scaleFactor = 1.1;  

    if (delta) {
        var factor = Math.pow(scaleFactor,delta);
        var transX = (-1/factor) * diff.x;
        var transY = (-1/factor) * diff.y;
        context.scale(factor,factor);
        context.translate(transX, transY);
    }
}