DOM 事件是否与指针锁定一起使用?

Do DOM events work with pointer lock?

我在 canvas 元素上使用了指针锁定,并且 canvas 全屏显示。我想检测右键单击和左键单击以响应它们。是否可以响应全屏和指针锁定的点击?我已经知道如何使用指针锁定 api 和全屏 api,我不需要任何解释如何使用它们的答案。任何帮助将不胜感激。

根据我所做的实验,简短的回答是 "it depends." 看看下面的演示。每个维度都有一个 canvas 缩放为屏幕尺寸的四分之一。当您将光标移到它上面时,canvas 上会出现一个白色圆圈。当您左键单击时,您将在 canvas 上绘制一个红色圆圈,而当您单击鼠标右键时,您将在 canvas 上绘制一个青色圆圈。当您单击 "Full screen" 按钮时,您将激活指针锁定并进入全屏模式。如果按 "Esc" 键,您将退出指针锁定和全屏模式。

请注意,您需要将代码复制并粘贴到文件中并加载它。如果您只单击 "Run code snippet."

,演示将不会 运行

关于你的问题,我知道有两个问题:

  1. 在 Chrome 中,即使在 fullscreen/pointer 锁定时,也会触发右键和左键单击事件。但是,在 Firefox 中,只会触发左键单击事件;我无法使用我尝试过的任何处理程序(clickmousedownmouseupcontextmenu)获得右键单击事件。当不处于 fullscreen/pointer 锁定状态时,左键和右键单击事件都会按预期在两个浏览器中触发。如果有人有任何在 fullscreen/pointer 锁定时收听右键单击事件的解决方案,我很想听听。
  2. 似乎 在两个 Chrome/Firefox 中的指针锁定中,事件不再向下渗透到具有指针锁定的元素中包含的元素,但它们继续冒泡到父元素。 因此在演示中,canvasdiv 中。 div 有指针锁定。 onclick 处理程序附加到 canvasdivdocument 以报告控制台中的点击事件。在没有指针锁定的情况下,单击 canvas 会触发所有三个元素(canvasdivdocument)的 onclick 处理程序。但是,在 div 上使用指针锁定后,canvasonclick 处理程序永远不会被触发,尽管 divdocument 的处理程序会触发。

我还发现了 Firefox 的其他一些怪癖,虽然与您最初的问题没有直接关系,但可能对有兴趣实现此类事情的人有所帮助:

  1. 当进入全屏模式时,Firefox 将样式应用于全屏元素以使其充满屏幕。当 canvas 全屏放置时,我无法正确设置样式(即占据全屏)。相反,我不得不将 canvas 包裹在 div 中并在 div 上进入全屏。有关详细信息,请参阅 Fullscreen API documentation on MDN

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. 在 Firefox 中,激活全屏模式会停用指针锁定。为了同时激活这两个功能,我必须先激活全屏模式,然后再激活指针锁定。然而简单的两行代码:

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

    没用。我对发生的事情的理解是,在完全建立全屏模式之前启动了对 requestPointerLock 的调用。这导致指针锁定被激活,然后又迅速停用。我发现有必要等到全屏模式完全建立后再调用 requestPointerLock()。检查 document.mozFullScreenElement !== null 似乎足以检查全屏模式是否完全可用。以下点击处理程序定义为我解决了这个问题:

    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);
    }
    

    此函数反复检查是否建立了全屏模式。当它是时,它启动指针锁定。如果2秒后无法确定全屏模式,则超时。

我没有在 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>

这对我在指针锁定后处理右键单击很有帮助。

const onMouseDown = (evt) => {
  switch (evt.which) {
    case 1: return handleLeftClick();
    case 3: return handleRightClick();
  }
};

document.body.addEventListener('mousedown', onMouseDown, true);