Skip to content

⬅️ Back to Table of Contents

📄 VRButton.js

📊 Analysis Summary

Metric Count
🔧 Functions 9
🧱 Classes 1
📊 Variables & Constants 2
⚡ Async/Await Patterns 3

📚 Table of Contents

🛠️ File Location:

📂 examples/jsm/webxr/VRButton.js

Variables & Constants

Name Type Kind Value Exported
currentSession any let/var null
sessionOptions any let/var { ...sessionInit, optionalFeatures: [ 'local-floor', 'bounded-floor', 'layers...

Async/Await Patterns

Type Function Await Expressions Promise Chains
promise-chain showEnterVR none navigator.xr.requestSession( 'immersive-vr', sessionOptions ).then, navigator.xr.offerSession( 'immersive-vr', sessionOptions )
.then( onSessionStarted ).catch, navigator.xr.offerSession( 'immersive-vr', sessionOptions ).then, navigator.xr.offerSession( 'immersive-vr', sessionOptions )
.then( onSessionStarted ).catch, navigator.xr.offerSession( 'immersive-vr', sessionOptions ).then
await-expression showEnterVR renderer.xr.setSession( session ) none
async-function onSessionStarted renderer.xr.setSession( session ) none

Functions

VRButton.createButton(renderer: any, sessionInit: XRSessionInit): HTMLElement

JSDoc:

/**
     * 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.
     */

Parameters:

  • renderer any
  • sessionInit XRSessionInit

Returns: HTMLElement

Calls:

  • document.createElement
  • session.addEventListener
  • renderer.xr.setSession
  • currentSession.removeEventListener
  • navigator.xr.requestSession( 'immersive-vr', sessionOptions ).then
  • currentSession.end
  • navigator.xr.offerSession( 'immersive-vr', sessionOptions ) .then( onSessionStarted ) .catch
  • console.warn
  • navigator.xr.offerSession( 'immersive-vr', sessionOptions ) .then( onSessionStarted ) .catch
  • disableButton
  • stylizeElement
  • `navigator.xr.isSessionSupported( 'immersive-vr' ).then( function ( supported ) {
            supported ? showEnterVR() : showWebXRNotFound();
    
            if ( supported && VRButton.xrSessionIsGranted ) {
    
                button.click();
    
            }
    
        } ).catch`
    
    • document.location.href.replace

Internal Comments:

// (x5)
// WebXR's requestReferenceSpace only works if the corresponding feature (x2)
// was requested at session creation time. For simplicity, just ask for (x2)
// the interesting ones as optional features, but be aware that the (x2)
// requestReferenceSpace call will fail if it turns out to be unavailable. (x2)
// ('local' is always available for immersive sessions and doesn't need to (x2)
// be requested separately.) (x2)

Code
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;

        }

    }

VRButton.registerSessionGrantedListener(): void

JSDoc:

/**
     * 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.
     */

Returns: void

Calls:

  • /WebXRViewer\//i.test
  • navigator.xr.addEventListener

Internal Comments:

// WebXRViewer (based on Firefox) has a bug where addEventListener
// throws a silent exception and aborts execution entirely.

Code
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;

            } );

        }

    }

showEnterVR(): void

Returns: void

Calls:

  • session.addEventListener
  • renderer.xr.setSession
  • currentSession.removeEventListener
  • navigator.xr.requestSession( 'immersive-vr', sessionOptions ).then
  • currentSession.end
  • navigator.xr.offerSession( 'immersive-vr', sessionOptions ) .then( onSessionStarted ) .catch
  • console.warn
  • navigator.xr.offerSession( 'immersive-vr', sessionOptions ) .then( onSessionStarted ) .catch

Internal Comments:

// (x5)
// WebXR's requestReferenceSpace only works if the corresponding feature (x2)
// was requested at session creation time. For simplicity, just ask for (x2)
// the interesting ones as optional features, but be aware that the (x2)
// requestReferenceSpace call will fail if it turns out to be unavailable. (x2)
// ('local' is always available for immersive sessions and doesn't need to (x2)
// be requested separately.) (x2)

Code
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 );

                    } );

            }

        }

onSessionStarted(session: any): Promise<void>

Parameters:

  • session any

Returns: Promise<void>

Calls:

  • session.addEventListener
  • renderer.xr.setSession
Code
async function onSessionStarted( session ) {

                session.addEventListener( 'end', onSessionEnded );

                await renderer.xr.setSession( session );
                button.textContent = 'EXIT VR';

                currentSession = session;

            }

onSessionEnded(): void

Returns: void

Calls:

  • currentSession.removeEventListener
Code
function onSessionEnded( /*event*/ ) {

                currentSession.removeEventListener( 'end', onSessionEnded );

                button.textContent = 'ENTER VR';

                currentSession = null;

            }

disableButton(): void

Returns: void

Code
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;

        }

showWebXRNotFound(): void

Returns: void

Calls:

  • disableButton
Code
function showWebXRNotFound() {

            disableButton();

            button.textContent = 'VR NOT SUPPORTED';

        }

showVRNotAllowed(exception: any): void

Parameters:

  • exception any

Returns: void

Calls:

  • disableButton
  • console.warn
Code
function showVRNotAllowed( exception ) {

            disableButton();

            console.warn( 'Exception when trying to call xr.isSessionSupported', exception );

            button.textContent = 'VR NOT ALLOWED';

        }

stylizeElement(element: any): void

Parameters:

  • element any

Returns: void

Code
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';

        }

Classes

VRButton

Class Code
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;

            } );

        }

    }

}

Methods

createButton(renderer: any, sessionInit: XRSessionInit): HTMLElement
Code
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;

        }

    }
registerSessionGrantedListener(): void
Code
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;

            } );

        }

    }