UNPKG

three

Version:

JavaScript 3D library

264 lines (168 loc) 5.79 kB
/** * A utility class for creating a button that allows to initiate * immersive VR sessions based on WebXR. The button can be created * with a factory method and then appended ot the website's DOM. * * ```js * document.body.appendChild( VRButton.createButton( renderer ) ); * ``` * * @hideconstructor */ class VRButton { /** * Constructs a new VR button. * * @param {WebGLRenderer|WebGPURenderer} renderer - The renderer. * @param {XRSessionInit} [sessionInit] - The a configuration object for the AR session. * @return {HTMLElement} The button or an error message if `immersive-ar` isn't supported. */ static createButton( renderer, sessionInit = {} ) { const button = document.createElement( 'button' ); function showEnterVR( /*device*/ ) { let currentSession = null; async function onSessionStarted( session ) { session.addEventListener( 'end', onSessionEnded ); await renderer.xr.setSession( session ); button.textContent = 'EXIT VR'; currentSession = session; } function onSessionEnded( /*event*/ ) { currentSession.removeEventListener( 'end', onSessionEnded ); button.textContent = 'ENTER VR'; currentSession = null; } // button.style.display = ''; button.style.cursor = 'pointer'; button.style.left = 'calc(50% - 50px)'; button.style.width = '100px'; button.textContent = 'ENTER VR'; // WebXR's requestReferenceSpace only works if the corresponding feature // was requested at session creation time. For simplicity, just ask for // the interesting ones as optional features, but be aware that the // requestReferenceSpace call will fail if it turns out to be unavailable. // ('local' is always available for immersive sessions and doesn't need to // be requested separately.) const sessionOptions = { ...sessionInit, optionalFeatures: [ 'local-floor', 'bounded-floor', 'layers', ...( sessionInit.optionalFeatures || [] ) ], }; button.onmouseenter = function () { button.style.opacity = '1.0'; }; button.onmouseleave = function () { button.style.opacity = '0.5'; }; button.onclick = function () { if ( currentSession === null ) { navigator.xr.requestSession( 'immersive-vr', sessionOptions ).then( onSessionStarted ); } else { currentSession.end(); if ( navigator.xr.offerSession !== undefined ) { navigator.xr.offerSession( 'immersive-vr', sessionOptions ) .then( onSessionStarted ) .catch( ( err ) => { console.warn( err ); } ); } } }; if ( navigator.xr.offerSession !== undefined ) { navigator.xr.offerSession( 'immersive-vr', sessionOptions ) .then( onSessionStarted ) .catch( ( err ) => { console.warn( err ); } ); } } function disableButton() { button.style.display = ''; button.style.cursor = 'auto'; button.style.left = 'calc(50% - 75px)'; button.style.width = '150px'; button.onmouseenter = null; button.onmouseleave = null; button.onclick = null; } function showWebXRNotFound() { disableButton(); button.textContent = 'VR NOT SUPPORTED'; } function showVRNotAllowed( exception ) { disableButton(); console.warn( 'Exception when trying to call xr.isSessionSupported', exception ); button.textContent = 'VR NOT ALLOWED'; } function stylizeElement( element ) { element.style.position = 'absolute'; element.style.bottom = '20px'; element.style.padding = '12px 6px'; element.style.border = '1px solid #fff'; element.style.borderRadius = '4px'; element.style.background = 'rgba(0,0,0,0.1)'; element.style.color = '#fff'; element.style.font = 'normal 13px sans-serif'; element.style.textAlign = 'center'; element.style.opacity = '0.5'; element.style.outline = 'none'; element.style.zIndex = '999'; } if ( 'xr' in navigator ) { button.id = 'VRButton'; button.style.display = 'none'; stylizeElement( button ); navigator.xr.isSessionSupported( 'immersive-vr' ).then( function ( supported ) { supported ? showEnterVR() : showWebXRNotFound(); if ( supported && VRButton.xrSessionIsGranted ) { button.click(); } } ).catch( showVRNotAllowed ); return button; } else { const message = document.createElement( 'a' ); if ( window.isSecureContext === false ) { message.href = document.location.href.replace( /^http:/, 'https:' ); message.innerHTML = 'WEBXR NEEDS HTTPS'; // TODO Improve message } else { message.href = 'https://immersiveweb.dev/'; message.innerHTML = 'WEBXR NOT AVAILABLE'; } message.style.left = 'calc(50% - 90px)'; message.style.width = '180px'; message.style.textDecoration = 'none'; stylizeElement( message ); return message; } } /** * Registers a `sessiongranted` event listener. When a session is granted, the {@link VRButton#xrSessionIsGranted} * flag will evaluate to `true`. This method is automatically called by the module itself so there * should be no need to use it on app level. */ static registerSessionGrantedListener() { if ( typeof navigator !== 'undefined' && 'xr' in navigator ) { // WebXRViewer (based on Firefox) has a bug where addEventListener // throws a silent exception and aborts execution entirely. if ( /WebXRViewer\//i.test( navigator.userAgent ) ) return; navigator.xr.addEventListener( 'sessiongranted', () => { VRButton.xrSessionIsGranted = true; } ); } } } /** * Whether a XR session has been granted or not. * * @static * @type {boolean} * @default false */ VRButton.xrSessionIsGranted = false; VRButton.registerSessionGrantedListener(); export { VRButton };