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()...