Search code examples
javascripthtmlcanvaspolymerweb-component

How to get the coordinates of click/tap on canvas in Polymer/WebComponent (when inside scrolled container)


I was aware of the usual method for finding the click position relative to the canvas, as has been documented before, which in brief is:

  1. Grab event.pageX and event.pageY values to get the position of the click relative to the document
  2. subtract the x and y coords of the canvas on the page, either with canvasEl.offsetLeft/canvas.El.offsetTop or using jQuery's $(canvasEl).offset(), recursing up through parents if necessary
  3. Subtract the scroll position of the page and any parent elements, if any.

Sadly, this doesn't work when both the following are true:

  1. The canvas is part of a Polymer (and I assume any other WebComponent solution that polyfills Shadow DOM) element
  2. The canvas is positioned within a scrolling parent. I did some investigating into this and it looked like Polymer was skipping an ancestor when crossing the shadow boundary, meaning the scroll offset isn't calculated correctly.

Solution

  • Whenever I've come across this problem, even outside of WebComponents, my initial reaction has always been why is it so hard to get the coordinates of a click on a canvas? Surely that's pretty crucial? Shouldn't the coordinates be calculated for you already?

    It turns out that they have, almost. As described elsewhere on SO, The canvas API has a method getBoundingClientRect() which returns a DOMRect (ClientRect in Chrome):

    {
        x: 348,
        y: 180.35000610351562,
        width: 128,
        height: 128,
        top: 180.35000610351562,
        right: 476,
        bottom: 308.3500061035156,
        left: 348
    }
    

    So the following will reliably get the coordinates of a tap/click relative to the canvas element, come scroll or high water:

    var x = event.pageX,
        y = event.pageY; // (or your preferred method, e.g. jQuery)
        bounds = event.target.getBoundingClientRect();
    
    x = x - bounds.left;
    y = y - bounds.top;
    

    The above is from Firefox; I found Chrome doesn't include the x and y properties, so probably best to stick to left and top. I haven't tested it on IE or Safari.

    I hope this proves useful to someone else - it's been driving me nuts over the past few months. Many thanks to the questions and answers linked to here.

    Finally, this answer is a neat solution whether you're using Polymer/WebComponents or not - it just so happens that that was my in-road into the problem and really threw the issue into relief.