The GUI Editor at gui.babylonjs.com (since Babylon.js 5.0 89,603 ; the Inspector opens it too) lays controls out visually and saves JSON, locally or on Babylon's snippet server. Pages load it with ui.parseFromURLAsync(url) or ui.parseFromSnippetAsync(id) and find controls by name to attach code. Any ADT produces the same JSON:
const GUI = BABYLON.GUI;
const ui = GUI.AdvancedDynamicTexture.CreateFullscreenUI('ui', true, scene);
const card = Object.assign(new GUI.Rectangle('card'), { width: '200px', height: '80px' });
card.addControl(GUI.Button.CreateSimpleButton('buy', 'Add to cart'));
ui.addControl(card);
const json = ui.serializeContent(); // what the editor saves
const copy = GUI.AdvancedDynamicTexture.CreateFullscreenUI('copy', true, scene);
copy.parseSerializedObject(json); // what parseFromURLAsync does
copy.getControlByName('buy').onPointerUpObservable.add(() => console.log('buy')); // code
console.log(`${JSON.stringify(json).length} characters; copy has ` +
`${copy.getDescendants().length} controls`);Output
4575 characters; copy has 3 controls
The JSON holds no behavior, so keep names unique for the code that finds controls.
<!doctype html>
<style>
body { margin: 0; background: #f7f3ec; font: 13px system-ui, sans-serif; color: #333; }
canvas { width: 100%; max-width: 600px; height: 250px; display: block; touch-action: none; outline: none; }
#json { max-width: 600px; height: 120px; overflow: auto; margin: 0; padding: 4px 8px; box-sizing: border-box; font: 11px monospace; white-space: pre-wrap; background: #fff; }
</style>
<canvas id="view"></canvas>
<pre id="json"></pre>
<script src="https://cdn.jsdelivr.net/npm/babylonjs@9.28.0/babylon.js"></script>
<script src="https://cdn.jsdelivr.net/npm/babylonjs-gui@9.28.0/babylon.gui.min.js"></script>
<script>
const canvas = document.getElementById('view');
const engine = new BABYLON.Engine(canvas, true);
const scene = new BABYLON.Scene(engine);
scene.clearColor = BABYLON.Color4.FromHexString('#e9e4dcff');
new BABYLON.ArcRotateCamera('cam', -Math.PI / 2, 1.2, 4, BABYLON.Vector3.Zero(), scene);
const GUI = BABYLON.GUI;
const ui = GUI.AdvancedDynamicTexture.CreateFullscreenUI('ui', true, scene);
const card = Object.assign(new GUI.Rectangle('card'), { width: '220px', height: '120px', left: '-130px',
background: 'white', cornerRadius: 10, color: '#c9bfae' });
card.addControl(Object.assign(new GUI.TextBlock('title', 'Salt and Saffron'),
{ top: '-28px', fontSize: 18, fontWeight: 'bold', color: '#222' }));
const buy = GUI.Button.CreateSimpleButton('buy', 'Add to cart');
Object.assign(buy, { width: '160px', height: '38px', top: '22px', background: '#1f5f8b', color: 'white', cornerRadius: 6 });
card.addControl(buy);
ui.addControl(card);
ui.addControl(Object.assign(new GUI.TextBlock('', 'built in code'), { left: '-130px', top: '-80px', fontSize: 13, color: '#555' }));
// What the editor saves, and what parseFromURLAsync / parseFromSnippetAsync read back
const json = ui.serializeContent();
const copy = GUI.AdvancedDynamicTexture.CreateFullscreenUI('copy', true, scene);
copy.parseSerializedObject(json);
const copied = copy.getControlByName('card');
copied.left = '130px'; // move the copy to the right half
copy.getControlByName('title').text = 'Salt and Saffron (copy)';
copy.addControl(Object.assign(new GUI.TextBlock('', 'parsed from JSON'), { left: '130px', top: '-80px', fontSize: 13, color: '#555' }));
// The JSON holds no behavior: find the control by its unique name and attach code
let clicks = 0;
copy.getControlByName('buy').onPointerUpObservable.add(() => {
copy.getControlByName('buy').textBlock.text = `Added ${++clicks}`;
});
document.getElementById('json').textContent = `${JSON.stringify(json).length} characters; copy has ` +
`${copy.getDescendants().length} controls\n\n` + JSON.stringify(json, null, 1).slice(0, 700) + ' ...';
engine.runRenderLoop(() => scene.render());
addEventListener('resize', () => engine.resize());
</script>