javascriptonclickdom-eventsfullscreenpointerlock

Do DOM events work with pointer lock?


I have used the pointer lock on my canvas element, and the canvas is on full screen. I want to detect right clicks and left clicks to respond to them. Is it possible to respond to clicks in full screen and pointer lock? I already know how to use the pointer lock api and the fullscreen api, I don't want any answers explaining how to use them. Any help would be appreciated.


Solution

  • Based on the experiments I've done, the short answer is "it depends." Take a look at the following demo. There is a canvas scaled to be a quarter of the screen size in each dimension. When you move the cursor over it, a white circle appears on the canvas. When you left click, you'll draw a red circle to the canvas, and when you right click, you'll draw a cyan circle to the canvas. When you click the "Full screen" button, you'll activate pointer lock and enter fullscreen mode. If you press the "Esc" key, you'll exit pointer lock and fullscreen mode.

    Note that you'll need to copy and paste the code into a file and load it. The demo won't run if you just click "Run code snippet."

    As far as your question, there are two issues, I'm aware of:

    1. In Chrome, both right- and left-click events are triggered even while in fullscreen/pointer lock. However, in Firefox, only left-click events are triggered; I was unable to get right-click events using any of the handlers I tried (click, mousedown, mouseup, contextmenu). When not in fullscreen/pointer lock, both left- and right-click events get triggered as expected in both browsers. If anyone has any solutions for listening to right-click events while in fullscreen/pointer lock, I'd love to hear them.
    2. It seems that in pointer lock in both Chrome/Firefox, events no longer trickle down to elements contained in the element with pointer lock, but they continue to bubble up to parent elements. So in the demo, the canvas is inside a div. The div has pointer lock. onclick handlers are attached to the canvas, div, and document to report click events in the console. Without pointer lock, clicking on the canvas triggers onclick handlers for all three elements (canvas, div, and document). However, with pointer lock on the div, the onclick handler for the canvas never gets triggered, though the handlers for the div and the document do.

    I also identified a couple other quirks to Firefox that, while not directly related to your initial question, might be helpful to folks interested in implementing this sort of thing:

    1. When fullscreen mode is entered, Firefox will apply styles to the fullscreen element to get it to fill the screen. I was unable to get the canvas styled correctly (i.e. to take up the full screen) when it was placed full screen. Rather, I had to wrap the canvas in a div and enter full screen on the div. See the Fullscreen API documentation on MDN for more info:

    if you're trying to emulate WebKit's behavior on Gecko, you need to place the element you want to present inside another element, which you'll make fullscreen instead, and use CSS rules to adjust the inner element to match the appearance you want.

    1. In Firefox, activating fullscreen mode deactivated pointer lock. In order to get both activated, I had to first activate fullscreen mode and then activate pointer lock. However the simple two lines of code:

      canvasContainer.requestFullscreen();
      canvasContainer.requestPointerLock();
      

      did not work. My understanding of what was happening is that the call to requestPointerLock got initiated before full screen mode was fully established. This led to pointer lock being activated and then quickly deactivated again. I found it necessary to wait until fullscreen mode was fully established before calling requestPointerLock(). Checking that document.mozFullScreenElement !== null seemed to be sufficient for checking that full screen mode was completely operational. The following following click handler definition worked to solve this problem for me:

      document.getElementById('fullscreen_button').onclick = function(e) {
             // When button is clicked, enter both full screen and pointer lock
          canvasContainer.requestFullscreen();
          var timeout = 2000;
          var interval = window.setInterval(function() {
              if (document.mozFullScreenElement !== null) {
                  window.clearInterval(interval);
                  canvasContainer.requestPointerLock();
              } else if (timeout <= 0) {
                  addErrorMessage('Unable to establish pointer lock.');
                  clearTimeout(interval);
              } else {
                  timeout -= 50;
              }
          }, 50);
      }
      

      This function repeatedly checks if full screen mode is established. When it is, it initiate pointer lock. If fullscreen mode can't be determined after 2 s, it times out.

    I haven't done any testing in IE.

    <!DOCTYPE HTML>
    <html lang="en-US">
    <head>
        <style>
        </style>
    </head>
    <body>
      <p id="msgs">Click 'Full screen' button below to go full screen. <br>
      Click the left mouse button to draw a red circle. <br>
      Click any other mouse button to draw a cyan circle. <br>
      Press the 'Esc' key to exit full screen.</p>
      <div id="canvas_container">
        <canvas id="canvas"> </canvas>
      </div>
      <br>
      <button id='fullscreen_button'>Full screen</button>
    </body>
      <script>
    
        // Display constants
        var CANVAS_BG_COLOR = 'rgb(75, 75, 75)';
        var LEFT_CLICK_COLOR = 'rgb(255, 150, 150)';
        var OTHER_CLICK_COLOR = 'rgb(150, 255, 255)';
        var CURSOR_COLOR = 'rgb(200, 200, 200)';
        var CANVAS_SCALING_FACTOR = 4;              // Ratio between screen dimension and canvas dimension before going full-screen
    
        // Store mouse position
        var mouseX, mouseY;
    
        // Setup onscreen canvas, smaller than the screen by a factor of CANVAS_SCALING_FACTOR
        var canvas = document.getElementById('canvas');
        var ctx = canvas.getContext('2d');
        canvas.width = screen.width/CANVAS_SCALING_FACTOR;
        canvas.height = screen.height/CANVAS_SCALING_FACTOR;
    
        // Create an offscreen canvas that's the same as the size of the screen
        var offscreenCanvas = document.createElement('canvas');
        var offscreenCtx = offscreenCanvas.getContext('2d');
        offscreenCanvas.width = screen.width;
        offscreenCanvas.height = screen.height;
        
        var canvasContainer = document.getElementById('canvas_container');
    
        // Radius of the circle drawn and of the circle cursor
        var circleRadius = 12;
        var cursorRadius = circleRadius/CANVAS_SCALING_FACTOR
    
        offscreenCtx.drawCircle = ctx.drawCircle = function (x, y, color, radius) {
            this.fillStyle = color;
            this.beginPath();
            this.arc(x, y, radius, 0, 2*Math.PI, true);
            this.fill();
        }
    
        offscreenCtx.clearCanvas = function() {
            this.fillStyle = CANVAS_BG_COLOR;
            this.fillRect(0, 0, this.canvas.width, this.canvas.height);
        }
    
        ctx.update = function() {
            // Copy the offscreen canvas, scaling down if not in full-screen mode
            this.drawImage(offscreenCanvas, 0, 0, offscreenCanvas.width, offscreenCanvas.height,
                                            0, 0, canvas.width, canvas.height);
            // Draw the cursor
            this.drawCircle(mouseX, mouseY, CURSOR_COLOR, cursorRadius);
        }
    
        function pointerLockActive() {
            return document.pointerLockElement===canvasContainer || document.mozPointerLockElement === canvasContainer;
        }
    
        // Perform initial canvas setup
        offscreenCtx.clearCanvas();
        ctx.update();
    
        // Setup pointerlock and fullscreen API functions for cross-browser support
        function addErrorMessage(msg) {
            document.getElementById('msgs').innerHTML += ('<br><font color="red">' + msg + '</font>');
        }
    
        canvasContainer.requestPointerLock = canvasContainer.requestPointerLock || canvasContainer.mozRequestPointerLock;
        canvasContainer.requestFullscreen = canvasContainer.webkitRequestFullscreen || canvasContainer.mozRequestFullScreen || canvasContainer.msRequestFullscreen
        if (!canvasContainer.requestPointerLock) addErrorMessage('Error: Pointer lock not available');
        if (!canvasContainer.requestFullscreen) addErrorMessage('Error: Full screen mode not available');
    
        canvasContainer.addEventListener('mousemove', function(e) {
    
            if (pointerLockActive()) {
                // If in pointer lock, then cursor positions need to be updated manually;
                // Normal cursor positions (e.g. e.clientX and e.clientY) don't get updated in pointer lock
                mouseX += e.movementX, mouseY += e.movementY;
    
                // Prevent the mouse from moving off-screen
                mouseX = Math.min(Math.max(0, mouseX), canvas.width);
                mouseY = Math.min(Math.max(0, mouseY), canvas.height);
            } else {
                // If pointer lock is inactive, then mouse position is just position relative to canvas offset
                mouseX = (e.pageX - canvas.offsetLeft)
                mouseY = (e.pageY - canvas.offsetTop)
            }
            ctx.update();   // Update the onscreen canvas
        }, false);
    
        // Handle entering and exiting pointer lock; pointer lock status is yoked to full screen status; both are entered and exited at the same time
        document.addEventListener('pointerlockchange', function(e) {
    
            if (!pointerLockActive()) {
                console.log('Pointer lock deactivated');
                canvas.width /= CANVAS_SCALING_FACTOR;
                canvas.height /= CANVAS_SCALING_FACTOR
                cursorRadius /= CANVAS_SCALING_FACTOR;
                
            } else {
                console.log('Pointer lock activated')
                canvas.width *= CANVAS_SCALING_FACTOR;
                canvas.height *= CANVAS_SCALING_FACTOR;
                cursorRadius *= CANVAS_SCALING_FACTOR;
    
                // Set the initial mouse position to be the middle of the canvas
                mouseX = screen.width/2, mouseY = screen.height/2;
            }
    
            // Update the onscreen canvas
            ctx.update();
        });
    
        document.getElementById('fullscreen_button').onclick = function(e) {
            // When button is clicked, enter both full screen and pointer lock
            canvasContainer.requestFullscreen();
            var timeout = 2000;
            var interval = window.setInterval(function() {
                if (document.mozFullScreenElement !== null) {
                    window.clearInterval(interval);
                    canvasContainer.requestPointerLock();
                } else if (timeout <= 0) {
                    addErrorMessage('Unable to establish pointer lock.');
                    clearTimeout(interval);
                } else {
                    timeout -= 50;
                }
            }, 50);
            
        }
    
        canvasContainer.onclick = function(e) {
    
            console.log('canvasContainer clicked');
    
            if (pointerLockActive())
                // If pointer lock is active, then use the mouseX and mouseY positions that are manually updated by the mousemove event handler
                var cursorX = mouseX, cursorY = mouseY;
            else
                // Otherwise use the mouse positions passed in the event object
                // If not in full screen mode, the cursor position has to be scaled up, because the mouse position is relative to the onscreen canvas, but we're drawing on the offscreen canvas, which is larger by a factor of fullscreenScale
                var cursorX = (e.pageX - canvas.offsetLeft)*CANVAS_SCALING_FACTOR, cursorY = (e.pageY - canvas.offsetTop)*CANVAS_SCALING_FACTOR;
    
            // If the left mouse button is clicked (e.which===1), draw a circle of one color
            // If any other mouse button is clicked, draw a circle of another color
            var color = e.which === 1 ? LEFT_CLICK_COLOR : OTHER_CLICK_COLOR;
            offscreenCtx.drawCircle(cursorX, cursorY, color, circleRadius);
            ctx.update();
        };
    
        // Detect canvas right-click events. Prevent default behavior (e.g. context menu display) and pass on to the onclick handler to do the rest of the work
        canvasContainer.oncontextmenu = function(e) {
    
            e.preventDefault();
            this.onclick(e);
        }
    
        canvas.onclick = function() {
            console.log('canvas clicked');
        }
    
        document.onclick = function() {
            console.log('document clicked');
        }
    
      </script>    
    
    </html>