1

I've set up a jsfiddle illustrating my situation: http://jsfiddle.net/j5o0w5qc/1/

Basically, I've got three nested HTML elements: a viewport div on the outside, a stage div in the middle, and a canvas on the inside. The stage div provides a perspective setting for 3d transformations applied to the canvas. The viewport has overflow: hidden; so we don't see anything outside of the viewport. It also has a listener attached, listening for mousedown.

In my actual app that I'm building, the canvas might be transformed to any arbitrary 3d transformation, involving translation and rotation in 3d space. What I would like to happen is for the viewport div to intercept a click, and draw a spot on the canvas in the place you clicked. I'm intercepting the event with the viewport div, and I'm using offsetX and offsetY in Chrome. This works great for Chrome, but I know I can't rely on offsetX and offsetY in other browsers, so I'd like to use pageX and pageY, normalized via jQuery, but I'm not sure quite how to do that.

What I've currently got in the jsfiddle works great in Chrome, except when you click in the viewport NOT on the canvas. When you click on the canvas, it draws a dot there, regardless of the canvas's transformation. Chrome is doing all the hard work and giving me exactly what I want with offsetX and offsetY. However, when you click in the viewport NOT on the canvas, I guess it's giving me offsetX and offsetY values relative to the viewport, rather than the canvas, and then interpreting that and drawing a dot on the canvas. For example, if I transform the canvas and then click in the upper right corner of the viewport, a dot appears in the upper right corner of the canvas, regardless of where that corner actually appears on the page.

In Firefox, however, it works great as long as there is no transformation applied to the canvas, but as soon as the canvas is transformed, all of a sudden, the dot being drawn is displaced, and I can't figure out how to take my pageX and pageY values and figure out exactly where in the canvas I am clicking.

Does anyone have any brilliant solutions? I've been bashing my head against this problem for far too long. I'm pretty sure I need to manually calculate some 3d transformation matrices or something, and I've spent hours writing methods to return the inverse of a matrix, and to multiply a matrix by a vector, and all sorts of stuff, but none of it has actually solved the problem for me, and I'm not sure what I'm missing.

Stackoverflow says code is required with jsfiddle links, so here's all my code:

HTML:

<div id="viewport">
    <div id="stage">
        <canvas id="myCanvas" width="300" height="300"></canvas>
    </div>
</div>

<div id="stuff">
    <button onclick="transformMe()">Transform</button>
    <input id="blah" type="text" size="45"></input>
</div>

CSS:

#viewport, #stage, #myCanvas {
    width: 300px;
    height: 300px;
    position: absolute;
    top: 0;
    left: 0;
}

#viewport {
    border: 1px solid #000;
    overflow: hidden;
}

#stage {
    perspective: 1000px;
    transform-style: preserve-3d;
}

#myCanvas {
    background-color: green;
    transform-style: preserve-3d;
}

#stuff {
    position: absolute;
    top: 350px;
}

Javascript:

var counter = 0;

$('#viewport').mousedown(function _drawOnCanvas (e)
{
    var c = document.getElementById("myCanvas");
    var ctx = c.getContext("2d");
    var xpos, ypos;

    if (typeof e.offsetX=='undefined')
    {
        xpos = e.pageX - $('#myCanvas').offset().left;
        ypos = e.pageY - $('#myCanvas').offset().top;
    }
    else
    {
        xpos = e.offsetX;
        ypos = e.offsetY;
    }


    ctx.fillRect(xpos-5, ypos-5, 10, 10);
});

function transformMe()
{
    counter++;
    var angle = (counter * 30) % 360;
    $('#myCanvas').css('transform','perspective(1000px) rotate3d(5,6,7,' + angle + 'deg)');
    $('input').val('counter: ' + counter + ', angle: ' + angle);
};
Kyle S
  • 93
  • 5
  • seems like there should be a solution using the actual matrices here, but i have yet to figure that out – Chet Feb 25 '17 at 08:49
  • https://stackoverflow.com/questions/55654650/how-to-get-a-canvas-relative-mouse-position-of-a-css-3d-transformed-canvas – gman Nov 20 '19 at 03:20

2 Answers2

4

For Firefox, you can use event.layerX and event.layerY. Think of them as Firefox's versions of offsetX & offsetY.

DEMO: http://jsfiddle.net/dirtyd77/j5o0w5qc/3/

JAVASCRIPT:

var counter = 0;

$('#viewport').mousedown(function _drawOnCanvas (e)
{
    var c = document.getElementById("myCanvas");
    var ctx = c.getContext("2d");
    var xpos, ypos;

    if (typeof e.offsetX=='undefined')
    {
        xpos = e.originalEvent.layerX;
        ypos = e.originalEvent.layerY;
    }
    else
    {
        xpos = e.offsetX;
        ypos = e.offsetY;
    }


    ctx.fillRect(xpos-5, ypos-5, 10, 10);
});

function transformMe()
{
    counter++;
    var angle = (counter * 30) % 360;
    $('#myCanvas').css('transform','perspective(1000px) rotate3d(5,6,7,' + angle + 'deg)');
    $('input').val('counter: ' + counter + ', angle: ' + angle);
};
Dom
  • 38,906
  • 12
  • 52
  • 81
  • Thanks; this might work for me. It's only one step in what I'm actually trying to solve, which is, get an updated mouse position when my `#myCanvas` has been transformed but the mouse has not been moved since the transformation. I believe if I take the former mouse position, multiply that vector by the inverse of the former transformation matrix, and then multiply the resulting vector by the current transformation matrix, that should do it. Right? – Kyle S Sep 30 '14 at 17:57
  • The latest Firefox supports offsetX and offsetY. However, they don't respect 3D transformed elements like in Chrome. Still trying to find solution to that. layerX still respects it in FireFox, but now it's harder to decide when to use layerX because I can no longer check if offsetX is undefined. Might need to add special check for Firefox now. – Jón Trausti Arason Nov 06 '15 at 15:57
1

If you change viewport to myCanvas in line 3 of the either Kyle S or Dom's jsfiddles:

$('#myCanvas').mousedown(function _drawOnCanvas (e)

it no longer places a dot when you "click in the viewport NOT on the canvas."

It seems there's a new issue with Firefox - if there's a transformation it only lets you paint on half ( the bottom left of diagonal - but depends on transformation ).

JJones
  • 802
  • 7
  • 7