Skip to content

⬅️ Back to Table of Contents

📄 XRButton.js

📊 Analysis Summary

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

📚 Table of Contents

🛠️ File Location:

📂 examples/jsm/webxr/XRButton.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 showStartXR none navigator.xr.requestSession( mode, sessionOptions ).then, navigator.xr.offerSession( mode, sessionOptions )
.then( onSessionStarted ).catch, navigator.xr.offerSession( mode, sessionOptions ).then, navigator.xr.offerSession( mode, sessionOptions )
.then( onSessionStarted ).catch, navigator.xr.offerSession( mode, sessionOptions ).then
await-expression showStartXR renderer.xr.setSession( session ) none
async-function onSessionStarted renderer.xr.setSession( session ) none

Functions

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

JSDoc:

/**
     * Constructs a new XR 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 WebXR isn't supported.
     */

Parameters:

  • renderer any
  • sessionInit XRSessionInit

Returns: HTMLElement

Calls:

  • document.createElement
  • session.addEventListener
  • renderer.xr.setSession
  • currentSession.removeEventListener
  • navigator.xr.requestSession( mode, sessionOptions ) .then
  • currentSession.end
  • navigator.xr.offerSession( mode, sessionOptions ) .then( onSessionStarted ) .catch
  • console.warn
  • navigator.xr.offerSession( mode, sessionOptions ) .then( onSessionStarted ) .catch
  • disableButton
  • stylizeElement
  • `navigator.xr.isSessionSupported( 'immersive-ar' ) .then( function ( supported ) {
                if ( supported ) {
    
                    showStartXR( 'immersive-ar' );
    
                } else {
    
                    navigator.xr.isSessionSupported( 'immersive-vr' )
                        .then( function ( supported ) {
    
                            if ( supported ) {
    
                                showStartXR( 'immersive-vr' );
    
                            } else {
    
                                showXRNotSupported();
    
                            }
    
                        } ).catch( showXRNotAllowed );
    
                }
    
            } ).catch`
    
    • document.location.href.replace

Internal Comments:

// (x5)

Code
static createButton( renderer, sessionInit = {} ) {

        const button = document.createElement( 'button' );

        function showStartXR( mode ) {

            let currentSession = null;

            async function onSessionStarted( session ) {

                session.addEventListener( 'end', onSessionEnded );

                await renderer.xr.setSession( session );

                button.textContent = 'STOP XR';

                currentSession = session;

            }

            function onSessionEnded( /*event*/ ) {

                currentSession.removeEventListener( 'end', onSessionEnded );

                button.textContent = 'START XR';

                currentSession = null;

            }

            //

            button.style.display = '';

            button.style.cursor = 'pointer';
            button.style.left = 'calc(50% - 50px)';
            button.style.width = '100px';

            button.textContent = 'START XR';

            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( mode, sessionOptions )
                        .then( onSessionStarted );

                } else {

                    currentSession.end();

                    if ( navigator.xr.offerSession !== undefined ) {

                        navigator.xr.offerSession( mode, sessionOptions )
                            .then( onSessionStarted )
                            .catch( ( err ) => {

                                console.warn( err );

                            } );

                    }

                }

            };

            if ( navigator.xr.offerSession !== undefined ) {

                navigator.xr.offerSession( mode, 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 showXRNotSupported() {

            disableButton();

            button.textContent = 'XR NOT SUPPORTED';

        }

        function showXRNotAllowed( exception ) {

            disableButton();

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

            button.textContent = 'XR 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 = 'XRButton';
            button.style.display = 'none';

            stylizeElement( button );

            navigator.xr.isSessionSupported( 'immersive-ar' )
                .then( function ( supported ) {

                    if ( supported ) {

                        showStartXR( 'immersive-ar' );

                    } else {

                        navigator.xr.isSessionSupported( 'immersive-vr' )
                            .then( function ( supported ) {

                                if ( supported ) {

                                    showStartXR( 'immersive-vr' );

                                } else {

                                    showXRNotSupported();

                                }

                            } ).catch( showXRNotAllowed );

                    }

                } ).catch( showXRNotAllowed );

            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;

        }

    }

showStartXR(mode: any): void

Parameters:

  • mode any

Returns: void

Calls:

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

Internal Comments:

// (x5)

Code
function showStartXR( mode ) {

            let currentSession = null;

            async function onSessionStarted( session ) {

                session.addEventListener( 'end', onSessionEnded );

                await renderer.xr.setSession( session );

                button.textContent = 'STOP XR';

                currentSession = session;

            }

            function onSessionEnded( /*event*/ ) {

                currentSession.removeEventListener( 'end', onSessionEnded );

                button.textContent = 'START XR';

                currentSession = null;

            }

            //

            button.style.display = '';

            button.style.cursor = 'pointer';
            button.style.left = 'calc(50% - 50px)';
            button.style.width = '100px';

            button.textContent = 'START XR';

            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( mode, sessionOptions )
                        .then( onSessionStarted );

                } else {

                    currentSession.end();

                    if ( navigator.xr.offerSession !== undefined ) {

                        navigator.xr.offerSession( mode, sessionOptions )
                            .then( onSessionStarted )
                            .catch( ( err ) => {

                                console.warn( err );

                            } );

                    }

                }

            };

            if ( navigator.xr.offerSession !== undefined ) {

                navigator.xr.offerSession( mode, 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 = 'STOP XR';

                currentSession = session;

            }

onSessionEnded(): void

Returns: void

Calls:

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

                currentSession.removeEventListener( 'end', onSessionEnded );

                button.textContent = 'START XR';

                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;

        }

showXRNotSupported(): void

Returns: void

Calls:

  • disableButton
Code
function showXRNotSupported() {

            disableButton();

            button.textContent = 'XR NOT SUPPORTED';

        }

showXRNotAllowed(exception: any): void

Parameters:

  • exception any

Returns: void

Calls:

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

            disableButton();

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

            button.textContent = 'XR 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

XRButton

Class Code
class XRButton {

    /**
     * Constructs a new XR 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 WebXR isn't supported.
     */
    static createButton( renderer, sessionInit = {} ) {

        const button = document.createElement( 'button' );

        function showStartXR( mode ) {

            let currentSession = null;

            async function onSessionStarted( session ) {

                session.addEventListener( 'end', onSessionEnded );

                await renderer.xr.setSession( session );

                button.textContent = 'STOP XR';

                currentSession = session;

            }

            function onSessionEnded( /*event*/ ) {

                currentSession.removeEventListener( 'end', onSessionEnded );

                button.textContent = 'START XR';

                currentSession = null;

            }

            //

            button.style.display = '';

            button.style.cursor = 'pointer';
            button.style.left = 'calc(50% - 50px)';
            button.style.width = '100px';

            button.textContent = 'START XR';

            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( mode, sessionOptions )
                        .then( onSessionStarted );

                } else {

                    currentSession.end();

                    if ( navigator.xr.offerSession !== undefined ) {

                        navigator.xr.offerSession( mode, sessionOptions )
                            .then( onSessionStarted )
                            .catch( ( err ) => {

                                console.warn( err );

                            } );

                    }

                }

            };

            if ( navigator.xr.offerSession !== undefined ) {

                navigator.xr.offerSession( mode, 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 showXRNotSupported() {

            disableButton();

            button.textContent = 'XR NOT SUPPORTED';

        }

        function showXRNotAllowed( exception ) {

            disableButton();

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

            button.textContent = 'XR 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 = 'XRButton';
            button.style.display = 'none';

            stylizeElement( button );

            navigator.xr.isSessionSupported( 'immersive-ar' )
                .then( function ( supported ) {

                    if ( supported ) {

                        showStartXR( 'immersive-ar' );

                    } else {

                        navigator.xr.isSessionSupported( 'immersive-vr' )
                            .then( function ( supported ) {

                                if ( supported ) {

                                    showStartXR( 'immersive-vr' );

                                } else {

                                    showXRNotSupported();

                                }

                            } ).catch( showXRNotAllowed );

                    }

                } ).catch( showXRNotAllowed );

            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;

        }

    }

}

Methods

createButton(renderer: any, sessionInit: XRSessionInit): HTMLElement
Code
static createButton( renderer, sessionInit = {} ) {

        const button = document.createElement( 'button' );

        function showStartXR( mode ) {

            let currentSession = null;

            async function onSessionStarted( session ) {

                session.addEventListener( 'end', onSessionEnded );

                await renderer.xr.setSession( session );

                button.textContent = 'STOP XR';

                currentSession = session;

            }

            function onSessionEnded( /*event*/ ) {

                currentSession.removeEventListener( 'end', onSessionEnded );

                button.textContent = 'START XR';

                currentSession = null;

            }

            //

            button.style.display = '';

            button.style.cursor = 'pointer';
            button.style.left = 'calc(50% - 50px)';
            button.style.width = '100px';

            button.textContent = 'START XR';

            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( mode, sessionOptions )
                        .then( onSessionStarted );

                } else {

                    currentSession.end();

                    if ( navigator.xr.offerSession !== undefined ) {

                        navigator.xr.offerSession( mode, sessionOptions )
                            .then( onSessionStarted )
                            .catch( ( err ) => {

                                console.warn( err );

                            } );

                    }

                }

            };

            if ( navigator.xr.offerSession !== undefined ) {

                navigator.xr.offerSession( mode, 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 showXRNotSupported() {

            disableButton();

            button.textContent = 'XR NOT SUPPORTED';

        }

        function showXRNotAllowed( exception ) {

            disableButton();

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

            button.textContent = 'XR 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 = 'XRButton';
            button.style.display = 'none';

            stylizeElement( button );

            navigator.xr.isSessionSupported( 'immersive-ar' )
                .then( function ( supported ) {

                    if ( supported ) {

                        showStartXR( 'immersive-ar' );

                    } else {

                        navigator.xr.isSessionSupported( 'immersive-vr' )
                            .then( function ( supported ) {

                                if ( supported ) {

                                    showStartXR( 'immersive-vr' );

                                } else {

                                    showXRNotSupported();

                                }

                            } ).catch( showXRNotAllowed );

                    }

                } ).catch( showXRNotAllowed );

            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;

        }

    }