PLock demo
HTML
<h1>Pointer-lock wheel target change</h1>
<p>
Click <b>Lock A</b>, then scroll. On scroll, the pointer lock target will
change to B.
</p>
<button id="lock">Lock A</button>
<p id="status">Pointer lock: none</p>
<div id="a" class="scroller" tabindex="0">
<div class="content">A — intended lock target. Scroll this content.</div>
</div>
<div id="b" class="scroller" tabindex="0">
<div class="content">B — new target if lock transfer succeeds.</div>
</div>
<pre id="log"></pre>
CSS
body {
font: 16px sans-serif;
margin: 20px;
}
.scroller {
border: 3px solid;
display: inline-block;
height: 220px;
margin: 12px 12px 12px 0;
overflow: auto;
vertical-align: top;
width: 280px;
}
#a { border-color: #1672d4; }
#b { border-color: #d33; }
.content {
background: repeating-linear-gradient(
#eee 0 38px, #ccc 38px 40px);
height: 1400px;
padding: 8px;
}
#log {
background: #111;
color: #b7f7b7;
height: 180px;
overflow: auto;
padding: 8px;
white-space: pre-wrap;
}
JavaScript
const a = document.getElementById('a');
const b = document.getElementById('b');
const status = document.getElementById('status');
const log = document.getElementById('log');
let actionTaken = false;
function record(message) {
log.textContent += `${performance.now().toFixed(1)} ms: ${message}\n`;
log.scrollTop = log.scrollHeight;
}
function reportState(prefix) {
const locked = document.pointerLockElement;
status.textContent =
`Pointer lock: ${locked ? locked.id : 'none'} | ` +
`A.scrollTop=${a.scrollTop} | B.scrollTop=${b.scrollTop}`;
record(`${prefix} lock=${locked ? locked.id : 'none'}, ` +
`A.scrollTop=${a.scrollTop}, B.scrollTop=${b.scrollTop}`);
}
document.addEventListener('pointerlockchange', () => {
reportState('pointerlockchange:');
});
document.addEventListener('pointerlockerror', () => {
record('pointerlockerror');
});
a.addEventListener('wheel', (event) => {
record(`wheel handler on A: event.target=${event.target.id || 'content'}, ` +
`deltaY=${event.deltaY}, lock at handler=${document.pointerLockElement?.id || 'none'}`);
if (actionTaken) {
return;
}
actionTaken = true;
record('wheel handler called B.requestPointerLock()');
b.requestPointerLock().then(
() => record('B.requestPointerLock() resolved'),
error => record(`B.requestPointerLock() rejected: ${error.name}`));
requestAnimationFrame(() => reportState('after wheel handler frame:'));
}, {capture: true});
for (const element of [a, b]) {
element.addEventListener('scroll', () => {
reportState(`scroll on ${element.id}:`);
});
}
document.getElementById('lock').addEventListener('click', () => {
actionTaken = false;
log.textContent = '';
a.requestPointerLock().then(
() => record('A.requestPointerLock() resolved'),
error => record(`A.requestPointerLock()...