Getting xrManagedOutputCanvas from createDefaultXRExperienceAsync for WebXREnterExitUIButton

I’m trying to replace the default VR button in WebXR, similar to this Playground example:

The problem I’m running into is getting the xrManagedOutputCanvas. The Playground example uses:

var xrHelper = await BABYLON.WebXRExperienceHelper.CreateAsync(scene)

// Create output canvas manager
var xrOutputCanvas = new BABYLON.WebXRManagedOutputCanvas(xrHelper);

while I’m trying to use:

const xrHelper = await scene.createDefaultXRExperienceAsync({

        optionalFeatures: true

    }).then(helper => { 


When I try to get the canvas context using the returned helper:

var xrOutputCanvas = new BABYLON.WebXRManagedOutputCanvas(helper, this._setup.getEngine().getRenderingCanvas());

Which is needed for:
var uiOptions = {outputCanvasContext: xrOutputCanvas.canvasContext, customButtons: }
var xrUI = await BABYLON.WebXREnterExitUI.CreateAsync(scene, xrHelper, uiOptions)

TypeScript complains:
error TS2345: Argument of type ‘WebXRExperienceHelper’ is not assignable to parameter of type ‘WebXRSessionManager’.
[ ts2] Type ‘WebXRExperienceHelper’ is missing the following properties from type ‘WebXRSessionManager’: _referenceSpace, _rttProvider, _sessionEnded, _xrNavigator, and 23 more.

So…how to get the canvas from the helper returned by createDefaultXRExperienceAsync? Or, is there a different way to change the default VR/AR buttons in BJS 4.2? Thanks, pete

Invoking the God of XR @RaananW


There are a few ways of changing the default button/UI. The first would be to implement your own button and add it to the list of custom buttons in the uiOptions of the default XR experience. The definition is here -

Babylon.js/webXREnterExitUI.ts at master · BabylonJS/Babylon.js (

The default XR experience takes UI Options as a variable so you can customize it as you see fit:

Babylon.js/webXRDefaultExperience.ts at master · BabylonJS/Babylon.js (

Besides that, the button is exposed under the classname xr-button-overlay , so you can modify its style using this class if you want.

Otherwise, you can define your own button/user interaction any way you want. The one important thing is that the button will call the helper’s enterXRAsync function with the right parameters. The default UI does it this way - Babylon.js/webXREnterExitUI.ts at master · BabylonJS/Babylon.js ( , but you can use whatever you want, as long as you pass the right variables.

I assume you found a playground in the docs, explaining how the default experience helpr works? otherwise, all of our examples use the default experience helper without modifying it too much.
The error in your code, by the way, is exactly what it says - you need to pass the session manager (part of the helper) and not the helper itself if you want to create the canvas output manager

1 Like

OK, I tried this method:

const xrHelper = await scene.createDefaultXRExperienceAsync({

        optionalFeatures: true

        // TODO: check additional features

    }).then(helper => {
             let manager = helper.baseExperience.sessionManager;

            // xrOutputCanvas
            var xrOutputCanvas = new BABYLON.WebXRManagedOutputCanvas(manager);

           // CHANGES THE BUTTON
           BABYLON.WebXREnterExitUI.CreateAsync(scene, helper.baseExperience, {
                customButtons: [{
                    element: xrButton,
                    referenceSpaceType: 'local-floor',
                    sessionMode: 'immersive-vr',
                    update: (active) => { console.log(active) }
                renderTarget: xrOutputCanvas


This compiles OK, but when I try to run, I get the following error:

WebGL: INVALID_OPERATION: bindFramebuffer: object does not belong to this context
localhost/:1 Note: The XRSession has completed multiple animation frames without drawing anything to the baseLayer’s framebuffer, resulting in no visible output.

I suspect I’m not giving it the right . TS complained when I tried to just give it the rendering canvas itself.

Or, does this have to be outside the .then() { … }

Just out of curiosity - is it your intention to replace those objects?

You can pass uiOptions directly to the default xr helper that will do exactly that what you are trying to achieve (without the need to pass a render target):

const xrHelper = await scene.createDefaultXRExperienceAsync({
        optionalFeatures: true,
        uiOptions: { ..... }

OK, I finally got it! I was confused between the method used to replace the object and just changing the button - long names! I had just been looking at the VR examples in Playground, many of which are obsolete. I looked at the AR examples, and it made sense. I’m writing it out in detail so I understand how the objects fit together:

const xrButton = document.getElementsByClassName(‘xr-toggle-button’)[0];

let xrOptions = new BABYLON.WebXRDefaultExperienceOptions();
xrOptions.optionalFeatures = true;
xrOptions.uiOptions = new BABYLON.WebXREnterExitUIOptions();
xrOptions.uiOptions.onError = (error) => { console.log(error) };

var hmdXRButton = new BABYLON.WebXREnterExitUIButton(xrButton, ‘immersive-vr’, ‘local-floor’);
xrOptions.uiOptions.customButtons = [hmdXRButton];

const xrHelper = await scene.createDefaultXRExperienceAsync(xrOptions).then(helper => {…

So that works (image attached).


So, the last parts I can’t figure out:

  1. Removing the box stroke around my SVG image.

  2. Add the additional button states, and/or different buttons for each enter/exit states. I can’t find an example in the Playground with more than one button. Ideally, I want to add different buttons for each state, otherwise change the button style.

Finally, I can’t find ennumeration of values for some of the options in WebXREnterExitUIOptions, e.g. .referenceSpaceType in the BabylonJS documentation.

The answer too all of your questions is - HTML and CSS :slight_smile:

This button is no more than a wrapper around the enter and exit functions of the experience helper using HTML click events. You can add any HTML element to your actual scene. The SVG can also be configured with CSS so that you don’t have a border or make any modifications you wish to it.

Not sure why you want a different state to the button (as the button is invisible in XR, but you can do that using the XR state change observable in the session manager. according to the state, define a different class to your button, and remove the others. This way you can configure the button in different states using CSS.

To make sure I understand - I just change the CSS to change appearance on state, and change by trapping the XR state change. Understand!

My question was whether you had to use MULTIPLE distinct HTML elements in the uioptions array, possibly the array position corresponding to the XR state. So, looks like you only need to add one - correct? Thanks again for responding - I had to switch for a few days to the Byzantine features of TypeScript + MongoDB - I’m linking a database to my BJS rendering of stars.

1 Like