<div class="canvas-container" id="canvas_container">
  <div class="banner"><div class="text">Device is presenting</div></div>
  <canvas id="webglCanvas"></canvas>
</div>
<div class="buttons">
  <button onclick="tryRequestPresent()">Request Present & Pointerlock</button> 
  <button onclick="tryExitPresent()">Exit Present</button>
  <button onclick="webglCanvas.requestPointerLock()">Request Pointerlock</button>
</div>

<h3>Log</h3>
<div id="log"></div>
body {
  font-family: segoe ui;
}

h3 {
  font-size: 20px;
}
.fail {
  word-wrap: break-word;
  color: red;
  font-weight: bold;
} 

div {
  font-size: 12px;
}
.note {
  word-wrap: break-word;
  color: blue;
  font-weight: bold;
} 
#canvas_container {
  height: 200px;
  width: 400px;
  float: right;
}
#canvas_container canvas {
  height: 100%;
  width: 100%;
}
#canvas_container .banner {
  display: none;
  height: 200px;
  width: 400px;
  background: grey;
  position: absolute;
}
#canvas_container .banner .text {
  color: white;
  font-weight: bold;
  padding: 15px;
}
#canvas_container.presenting.external .banner {
  display: inherit;
  
}
"use strict";

var webglCanvas = document.getElementById("webglCanvas"),
    gl = webglCanvas.getContext("experimental-webgl"),
    animationFrameId,
    vrDisplay = null,
    mousePressed = null,
    vrFrameData = new VRFrameData(),
    pulse = 0;

function initPointerLock() {
    // Register for events that are fired when the VRDisplay takes input from & returns input to the page.
    // We need to request & release pointerlock in response to these, otherwise mouse input is lost.
    window.addEventListener('vrdisplaypointerrestricted', onvrdisplaypointerrestricted, false);
    window.addEventListener('vrdisplaypointerunrestricted', onvrdisplaypointerunrestricted, false);  
    window.addEventListener('vrdisplaypresentchange', onvrdisplaypresentchange, false);
  
    displayNote('Registered for pointer restriction events.');

    function onvrdisplaypointerrestricted(e) {
        displayNote('Requesting Pointerlock in response to VDisplayPointerRestricted event.');
        webglCanvas.requestPointerLock();
    }
    function onvrdisplaypointerunrestricted() {
        displayNote('Exiting Pointerlock in response to VDisplayPointerUnrestricted event.');
        document.exitPointerLock();
    }
    function onvrdisplaypresentchange() {
        // Part 1/2 of Fallback method for browsers that do not raise pointerrestricted events.
        // We request pointer lock below, in tryRequestPresent()
        if (!vrDisplay.isPresenting) document.exitPointerLock();
    }

    // Listen for pointerlock change events, so we can in turn start listening for mouse click.  
    let mousedownListener = (e) => mousePressed = e.button,
        mouseupListener = () => mousePressed = null;
    document.addEventListener("pointerlockchange", (e) => {
        if (document.pointerLockElement) {
            displayNote('pointerlockchange event handler - pointerlock engaged');
            webglCanvas.addEventListener('mousedown', mousedownListener);
            webglCanvas.addEventListener('mouseup', mouseupListener);
        }
        else {
            displayNote('pointerlockchange event handler - pointerlock disabled');
            webglCanvas.removeEventListener('mousedown', mousedownListener);
            webglCanvas.removeEventListener('mouseup', mouseupListener);
        }
    });
}

// Clear the screen color based on the mouse button state.
function draw() {

    pulse  = ++pulse % 100;
    let val = Math.sin(pulse * 0.06283) * 0.25 + 0.75;
    gl.clearColor(
        mousePressed !== null ? 0.0 : val, // No Mouse Button
        mousePressed === 0 ? val : 0.0,    // Mouse Button 0
        mousePressed >= 1 ? val : 0.0,     // Mouse Button 1+
        1.0
    );
    gl.clear(gl.COLOR_BUFFER_BIT);
}

function tryRequestPresent() {
  
    if (!vrDisplay) return displayRejection(null, "Please plug in an HMD");  
  
    // Part 2/2 of Fallback method for browsers that do not raise pointerrestricted events.
    if (typeof(window.onvrdisplaypointerrestricted) === 'undefined') {
        displayNote('requesting pointerlock in absense of window.vrdisplaypointerrestricted');
        webglCanvas.requestPointerLock();
    }

    vrDisplay.requestPresent([{source: webglCanvas}]).then(() => {
        displayNote('Entered VR on ' + vrDisplay.displayName);
    }, (e) => {
        displayRejection(e, "requestPresent rejected");
    });  
}

// !!!!!!!!!!
// Helper functions below here!
// !!!!!!!!!!
function onAnimationFrame() {

    draw();
    
    // Indicate that we are ready to present the rendered frame to the VRDisplay
    if (vrDisplay) { 
        vrDisplay.requestAnimationFrame(onAnimationFrame);
      
        vrDisplay.getFrameData(vrFrameData);
        if (vrDisplay.isPresenting) {
          vrDisplay.submitFrame();
        }
    } else {
        window.requestAnimationFrame(onAnimationFrame);
    }
}

function initVR() {  
    window.addEventListener('vrdisplayconnect', (event) => { 
      vrDisplay = event.display; 
      displayNote('VRDisplayConnect: ' + (event&&event.display?event.display.displayName:'Unknown'));
    }, false );
    window.addEventListener('vrdisplaydisconnect', (event) => vrdisplay = null, false);

    window.addEventListener('vrdisplaypresentchange', (d) => {  
      let className = '';
      if (vrDisplay.isPresenting) {
        className += 'presenting';
        className += vrDisplay.capabilities.hasExternalDisplay ? ' external' : ' non-external';
      } else {
        className = '';
      }
      document.getElementById('canvas_container').className = className;
    });

    navigator.getVRDisplays().then((displays) => {
      if (displays.length !== 1) {
        return displayRejection(null, "getVRDisplays.length is 0 - plug in your HMD now.");
      }
      vrDisplay = displays[0];
    }, (e) => {
      displayRejection(e, "getVRDisplays rejected");
    });

    window.requestAnimationFrame(onAnimationFrame);
}


function tryExitPresent() {          
    vrDisplay && vrDisplay.exitPresent().then(() => displayNote('Exited VR.'), displayRejection);
}

function displayNote(message) {
    let elem = document.getElementById('log');
    elem.innerHTML = '<div class="note">' + message + '</div>' + elem.innerHTML;
}

function displayRejection(e, message) {
    let output = message + (e ? (': ' + (e.message || e)) : ''),
        elem = document.getElementById('log');
    elem.innerHTML = '<div class="fail">' + output + '</div>' + elem.innerHTML;
}

initVR();
initPointerLock();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.