Skip to content

⬅️ Back to Table of Contents

📄 ARButton.js

📊 Analysis Summary

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

📚 Table of Contents

🛠️ File Location:

📂 examples/jsm/webxr/ARButton.js

Variables & Constants

Name Type Kind Value Exported
currentSession any let/var null

Async/Await Patterns

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

Functions

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

JSDoc:

/**
     * Constructs a new AR 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
  • document.body.appendChild
  • document.createElementNS
  • svg.setAttribute
  • svg.addEventListener
  • currentSession.end
  • overlay.appendChild
  • path.setAttribute
  • svg.appendChild
  • sessionInit.optionalFeatures.push
  • session.addEventListener
  • renderer.xr.setReferenceSpaceType
  • renderer.xr.setSession
  • currentSession.removeEventListener
  • navigator.xr.requestSession( 'immersive-ar', sessionInit ).then
  • navigator.xr.offerSession( 'immersive-ar', sessionInit ) .then( onSessionStarted ) .catch
  • console.warn
  • navigator.xr.offerSession( 'immersive-ar', sessionInit ) .then( onSessionStarted ) .catch
  • disableButton
  • stylizeElement
  • `navigator.xr.isSessionSupported( 'immersive-ar' ).then( function ( supported ) {
            supported ? showStartAR() : showARNotSupported();
    
        } ).catch`
    
    • document.location.href.replace

Internal Comments:

// (x7)

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

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

        function showStartAR( /*device*/ ) {

            if ( sessionInit.domOverlay === undefined ) {

                const overlay = document.createElement( 'div' );
                overlay.style.display = 'none';
                document.body.appendChild( overlay );

                const svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
                svg.setAttribute( 'width', 38 );
                svg.setAttribute( 'height', 38 );
                svg.style.position = 'absolute';
                svg.style.right = '20px';
                svg.style.top = '20px';
                svg.addEventListener( 'click', function () {

                    currentSession.end();

                } );
                overlay.appendChild( svg );

                const path = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' );
                path.setAttribute( 'd', 'M 12,12 L 28,28 M 28,12 12,28' );
                path.setAttribute( 'stroke', '#fff' );
                path.setAttribute( 'stroke-width', 2 );
                svg.appendChild( path );

                if ( sessionInit.optionalFeatures === undefined ) {

                    sessionInit.optionalFeatures = [];

                }

                sessionInit.optionalFeatures.push( 'dom-overlay' );
                sessionInit.domOverlay = { root: overlay };

            }

            //

            let currentSession = null;

            async function onSessionStarted( session ) {

                session.addEventListener( 'end', onSessionEnded );

                renderer.xr.setReferenceSpaceType( 'local' );

                await renderer.xr.setSession( session );

                button.textContent = 'STOP AR';
                sessionInit.domOverlay.root.style.display = '';

                currentSession = session;

            }

            function onSessionEnded( /*event*/ ) {

                currentSession.removeEventListener( 'end', onSessionEnded );

                button.textContent = 'START AR';
                sessionInit.domOverlay.root.style.display = 'none';

                currentSession = null;

            }

            //

            button.style.display = '';

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

            button.textContent = 'START AR';

            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-ar', sessionInit ).then( onSessionStarted );

                } else {

                    currentSession.end();

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

                        navigator.xr.offerSession( 'immersive-ar', sessionInit )
                            .then( onSessionStarted )
                            .catch( ( err ) => {

                                console.warn( err );

                            } );

                    }

                }

            };

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

                navigator.xr.offerSession( 'immersive-ar', sessionInit )
                    .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 showARNotSupported() {

            disableButton();

            button.textContent = 'AR NOT SUPPORTED';

        }

        function showARNotAllowed( exception ) {

            disableButton();

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

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

            stylizeElement( button );

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

                supported ? showStartAR() : showARNotSupported();

            } ).catch( showARNotAllowed );

            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;

        }

    }

showStartAR(): void

Returns: void

Calls:

  • document.createElement
  • document.body.appendChild
  • document.createElementNS
  • svg.setAttribute
  • svg.addEventListener
  • currentSession.end
  • overlay.appendChild
  • path.setAttribute
  • svg.appendChild
  • sessionInit.optionalFeatures.push
  • session.addEventListener
  • renderer.xr.setReferenceSpaceType
  • renderer.xr.setSession
  • currentSession.removeEventListener
  • navigator.xr.requestSession( 'immersive-ar', sessionInit ).then
  • navigator.xr.offerSession( 'immersive-ar', sessionInit ) .then( onSessionStarted ) .catch
  • console.warn
  • navigator.xr.offerSession( 'immersive-ar', sessionInit ) .then( onSessionStarted ) .catch

Internal Comments:

// (x7)

Code
function showStartAR( /*device*/ ) {

            if ( sessionInit.domOverlay === undefined ) {

                const overlay = document.createElement( 'div' );
                overlay.style.display = 'none';
                document.body.appendChild( overlay );

                const svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
                svg.setAttribute( 'width', 38 );
                svg.setAttribute( 'height', 38 );
                svg.style.position = 'absolute';
                svg.style.right = '20px';
                svg.style.top = '20px';
                svg.addEventListener( 'click', function () {

                    currentSession.end();

                } );
                overlay.appendChild( svg );

                const path = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' );
                path.setAttribute( 'd', 'M 12,12 L 28,28 M 28,12 12,28' );
                path.setAttribute( 'stroke', '#fff' );
                path.setAttribute( 'stroke-width', 2 );
                svg.appendChild( path );

                if ( sessionInit.optionalFeatures === undefined ) {

                    sessionInit.optionalFeatures = [];

                }

                sessionInit.optionalFeatures.push( 'dom-overlay' );
                sessionInit.domOverlay = { root: overlay };

            }

            //

            let currentSession = null;

            async function onSessionStarted( session ) {

                session.addEventListener( 'end', onSessionEnded );

                renderer.xr.setReferenceSpaceType( 'local' );

                await renderer.xr.setSession( session );

                button.textContent = 'STOP AR';
                sessionInit.domOverlay.root.style.display = '';

                currentSession = session;

            }

            function onSessionEnded( /*event*/ ) {

                currentSession.removeEventListener( 'end', onSessionEnded );

                button.textContent = 'START AR';
                sessionInit.domOverlay.root.style.display = 'none';

                currentSession = null;

            }

            //

            button.style.display = '';

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

            button.textContent = 'START AR';

            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-ar', sessionInit ).then( onSessionStarted );

                } else {

                    currentSession.end();

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

                        navigator.xr.offerSession( 'immersive-ar', sessionInit )
                            .then( onSessionStarted )
                            .catch( ( err ) => {

                                console.warn( err );

                            } );

                    }

                }

            };

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

                navigator.xr.offerSession( 'immersive-ar', sessionInit )
                    .then( onSessionStarted )
                    .catch( ( err ) => {

                        console.warn( err );

                    } );

            }

        }

onSessionStarted(session: any): Promise<void>

Parameters:

  • session any

Returns: Promise<void>

Calls:

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

                session.addEventListener( 'end', onSessionEnded );

                renderer.xr.setReferenceSpaceType( 'local' );

                await renderer.xr.setSession( session );

                button.textContent = 'STOP AR';
                sessionInit.domOverlay.root.style.display = '';

                currentSession = session;

            }

onSessionEnded(): void

Returns: void

Calls:

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

                currentSession.removeEventListener( 'end', onSessionEnded );

                button.textContent = 'START AR';
                sessionInit.domOverlay.root.style.display = 'none';

                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;

        }

showARNotSupported(): void

Returns: void

Calls:

  • disableButton
Code
function showARNotSupported() {

            disableButton();

            button.textContent = 'AR NOT SUPPORTED';

        }

showARNotAllowed(exception: any): void

Parameters:

  • exception any

Returns: void

Calls:

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

            disableButton();

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

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

ARButton

Class Code
class ARButton {

    /**
     * Constructs a new AR 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 showStartAR( /*device*/ ) {

            if ( sessionInit.domOverlay === undefined ) {

                const overlay = document.createElement( 'div' );
                overlay.style.display = 'none';
                document.body.appendChild( overlay );

                const svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
                svg.setAttribute( 'width', 38 );
                svg.setAttribute( 'height', 38 );
                svg.style.position = 'absolute';
                svg.style.right = '20px';
                svg.style.top = '20px';
                svg.addEventListener( 'click', function () {

                    currentSession.end();

                } );
                overlay.appendChild( svg );

                const path = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' );
                path.setAttribute( 'd', 'M 12,12 L 28,28 M 28,12 12,28' );
                path.setAttribute( 'stroke', '#fff' );
                path.setAttribute( 'stroke-width', 2 );
                svg.appendChild( path );

                if ( sessionInit.optionalFeatures === undefined ) {

                    sessionInit.optionalFeatures = [];

                }

                sessionInit.optionalFeatures.push( 'dom-overlay' );
                sessionInit.domOverlay = { root: overlay };

            }

            //

            let currentSession = null;

            async function onSessionStarted( session ) {

                session.addEventListener( 'end', onSessionEnded );

                renderer.xr.setReferenceSpaceType( 'local' );

                await renderer.xr.setSession( session );

                button.textContent = 'STOP AR';
                sessionInit.domOverlay.root.style.display = '';

                currentSession = session;

            }

            function onSessionEnded( /*event*/ ) {

                currentSession.removeEventListener( 'end', onSessionEnded );

                button.textContent = 'START AR';
                sessionInit.domOverlay.root.style.display = 'none';

                currentSession = null;

            }

            //

            button.style.display = '';

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

            button.textContent = 'START AR';

            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-ar', sessionInit ).then( onSessionStarted );

                } else {

                    currentSession.end();

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

                        navigator.xr.offerSession( 'immersive-ar', sessionInit )
                            .then( onSessionStarted )
                            .catch( ( err ) => {

                                console.warn( err );

                            } );

                    }

                }

            };

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

                navigator.xr.offerSession( 'immersive-ar', sessionInit )
                    .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 showARNotSupported() {

            disableButton();

            button.textContent = 'AR NOT SUPPORTED';

        }

        function showARNotAllowed( exception ) {

            disableButton();

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

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

            stylizeElement( button );

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

                supported ? showStartAR() : showARNotSupported();

            } ).catch( showARNotAllowed );

            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 showStartAR( /*device*/ ) {

            if ( sessionInit.domOverlay === undefined ) {

                const overlay = document.createElement( 'div' );
                overlay.style.display = 'none';
                document.body.appendChild( overlay );

                const svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
                svg.setAttribute( 'width', 38 );
                svg.setAttribute( 'height', 38 );
                svg.style.position = 'absolute';
                svg.style.right = '20px';
                svg.style.top = '20px';
                svg.addEventListener( 'click', function () {

                    currentSession.end();

                } );
                overlay.appendChild( svg );

                const path = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' );
                path.setAttribute( 'd', 'M 12,12 L 28,28 M 28,12 12,28' );
                path.setAttribute( 'stroke', '#fff' );
                path.setAttribute( 'stroke-width', 2 );
                svg.appendChild( path );

                if ( sessionInit.optionalFeatures === undefined ) {

                    sessionInit.optionalFeatures = [];

                }

                sessionInit.optionalFeatures.push( 'dom-overlay' );
                sessionInit.domOverlay = { root: overlay };

            }

            //

            let currentSession = null;

            async function onSessionStarted( session ) {

                session.addEventListener( 'end', onSessionEnded );

                renderer.xr.setReferenceSpaceType( 'local' );

                await renderer.xr.setSession( session );

                button.textContent = 'STOP AR';
                sessionInit.domOverlay.root.style.display = '';

                currentSession = session;

            }

            function onSessionEnded( /*event*/ ) {

                currentSession.removeEventListener( 'end', onSessionEnded );

                button.textContent = 'START AR';
                sessionInit.domOverlay.root.style.display = 'none';

                currentSession = null;

            }

            //

            button.style.display = '';

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

            button.textContent = 'START AR';

            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-ar', sessionInit ).then( onSessionStarted );

                } else {

                    currentSession.end();

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

                        navigator.xr.offerSession( 'immersive-ar', sessionInit )
                            .then( onSessionStarted )
                            .catch( ( err ) => {

                                console.warn( err );

                            } );

                    }

                }

            };

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

                navigator.xr.offerSession( 'immersive-ar', sessionInit )
                    .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 showARNotSupported() {

            disableButton();

            button.textContent = 'AR NOT SUPPORTED';

        }

        function showARNotAllowed( exception ) {

            disableButton();

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

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

            stylizeElement( button );

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

                supported ? showStartAR() : showARNotSupported();

            } ).catch( showARNotAllowed );

            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;

        }

    }