2

I am writing an application that draws rectangles on a HTML canvas using the fillRect function. I currently track the movement of the mouse and detect when the mouse pointer hovers over a rectangle to highlight it:

This is how I am currently detecting collision which works great.

//boxes2 is my array of rectangles
var l = boxes2.length;

    for (var i = l - 1; i >= 0; i--) {

        if (mouseX >= boxes2[i].x  && mouseX <= (boxes2[i].x  + boxes2[i].w ) &&
            mouseY >= boxes2[i].y  && mouseY <= (boxes2[i].y  + boxes2[i].h )) {

            selectedBoxNum = i;

        }

    }

My problem is that this hover detection no longer works well after zooming in/out as the actual bounds of the rectangles desync from their values in my rectangle array.

var currentZoomValue = 1;
function myOnMouseWheel(event) {

    event.preventDefault();

    // Normalize wheel to +1 or -1.
    var wheel = event.wheelDelta / 120;

    if (wheel == 1) {
        zoom = 1.1;
    }
    else {
        zoom = .9;
    }

    currentZoomValue = currentZoomValue * zoom
    canvas.style.transform = "scale(" + currentZoomValue + ")";

}

What I have tried:

Scaling the values in the array as I zoom in/out so that the rectangle bounds will stay in sync

This will not work for me because the scale function is stretching my canvas to make the rectangles look bigger. If I also actually make them bigger, they will be doubly enlarged and outpace the zoom of my canvas background.

Compensating my hover detection based upon my current zoom level

I have tried something like:

if (mouseX >= boxes2[i].x  && mouseX <= (boxes2[i].x  + (boxes2[i].w * currentZoomValue) ) && 
mouseY >= boxes2[i].y  && mouseY <= (boxes2[i].y  + (boxes2[i].h * currentZoomValue) )) {

        selectedBoxNum = i;

    }

My attempts at this do not work because while the rectangle height and width do scale in an easily predictable way, the x,y coordinates do not. When zooming in, the rectangles will radiate out from the center so some rectangles will gain x value and other lose based upon their position. I also considered maintaining a second rectangle array that I could use just for hover detection but decided against it for this reason.

A good solution would be to actually scale the rectangle's sizes to give the illusion of zooming, but the rectangles positions on the background image is important, and this technique will not affect the background.

bradyGilley
  • 110
  • 3
  • 11
  • http://stackoverflow.com/questions/40600192/how-to-get-mouse-position-on-transformed-html5-canvas/40611738#40611738 – Kaiido Dec 27 '16 at 07:38

1 Answers1

1

Since there is no standard way of knowing page zoom level, I would suggest catching the click event with an absolutely-positioned div.

You can get the offset of your canvas element with the getBoundingClientRect() method.

Then, the code would look something like this:

boxes2.forEach(function(box, i) {

    cx.fillRect(box.x, box.y, box.w, box.h);

    /* We create an empty div */
    var div = document.createElement("div");
    /* We get the position of the canvas */
    var rect = canvas.getBoundingClientRect();

    div.style.position = "absolute";
    div.style.left = (rect.left + box.x) + "px"; //Don't forget the pixels!!!
    div.style.top = (rect.top + box.y) + "px";
    div.style.width = box.w + "px";
    div.style.height = box.h + "px";

    /* For demonstration purposes we display a border */
    div.style.border = "1px dashed black"

    div.onclick = function() {/* Your event handler */}

    document.body.appendChild(div);

});

Here's a live demonstration. At least in my browser, regions stay consistent even if I zoom in and out the page.

Community
  • 1
  • 1
mauroc8
  • 447
  • 1
  • 3
  • 9
  • I am zooming using the scale() method and not the browser zoom. GetBoundingClientRect does not seem to help to keep the div aligned with my rectangles in my testing. – bradyGilley Dec 27 '16 at 15:35
  • Are you using `context.scale()` method or css `transform:scale()` property ? You say that when zooming in the rectangles radiate from the center. Are you using `context.translate()` to some specific point before scaling ? – mauroc8 Dec 28 '16 at 07:29
  • 1
    I was using transform:scale(). While your answer did not solve my issue directly, it did inspire me to place invisible divs underneath the rectangle which scale properly with the rectangles and can handle my hover detection thanks. – bradyGilley Dec 28 '16 at 14:59