This isn't especially clever or tricky code, it's just a bundle of logic that I've found useful in past tinkering and generally doesn't need a lot of customization.
The idea is that this class handles the whole "lock mouse pointer on click"
operation and registers/unregisters event listeners to give an HTML5 canvas
game a nice "click to enter" sort of experience. The getInputFrame
method is then called once per game update tick to get new keyboard/mouse
inputs to respond to.
/** Manages the HTML5 PointerLock API in response to interaction with a * particular DOM element (usually a canvas) and collects user input for * processing by game logic. */ class InputRelay { constructor(domElement) { this.domElement = domElement; this.pointerLocked = false; this.keysDown = {}; this.prevKeys = {}; this.mouseX = 0; this.mouseY = 0; this.wheelY = 0; this.handleInput = this.handleInput.bind(this); this.domElement.addEventListener('click', this.handleInput); document.addEventListener('pointerlockchange', this.handleInput); } handleInput(evt) { if (evt.type == 'click' && !this.pointerLocked) { this.domElement.requestPointerLock(); } if (evt.type == 'pointerlockchange') { const prevLocked = this.pointerLocked; this.pointerLocked = (document.pointerLockElement === this.domElement); if (this.pointerLocked && !prevLocked) { document.addEventListener('mousemove', this.handleInput); document.addEventListener('mousedown', this.handleInput); document.addEventListener('mouseup', this.handleInput); document.addEventListener('keydown', this.handleInput); document.addEventListener('keyup', this.handleInput); document.addEventListener('wheel', this.handleInput); } if (!this.pointerLocked && prevLocked) { document.removeEventListener('mousemove', this.handleInput); document.removeEventListener('mousedown', this.handleInput); document.removeEventListener('mouseup', this.handleInput); document.removeEventListener('keydown', this.handleInput); document.removeEventListener('keyup', this.handleInput); document.removeEventListener('wheel', this.handleInput); } } if (evt.type == 'mousemove') { this.mouseX += evt.movementX; this.mouseY += evt.movementY; } if (evt.type == 'mousedown' || evt.type == 'mouseup') { this.keysDown['Mouse' + evt.which] = (evt.type == 'mousedown'); } if (evt.type == 'keydown' || evt.type == 'keyup') { this.keysDown[evt.code] = (evt.type == 'keydown'); } if (evt.type == 'wheel') { this.wheelY += evt.deltaY; } } getInputFrame() { const input = { pointerLocked: this.pointerLocked, mouseX: this.mouseX, mouseY: this.mouseY, wheelY: this.wheelY, keysDown: {}, prevKeys: {}, }; for (const key in this.prevKeys) { input.prevKeys[key] = this.prevKeys[key]; } for (const key in this.keysDown) { input.keysDown[key] = this.keysDown[key]; this.prevKeys[key] = this.keysDown[key]; } this.mouseX = this.mouseY = 0; this.wheelY = 0; return input; } }