Accessing & Manipulating the Unity WebGL Canvas

Unity WebGL can display 3D content because it uses a web standard called WebGL.
I’m sure we all figured that out by the platform build name.
It is basically a web version of an open-spec and mature 3D graphics API called OpenGL. WebGL works by drawing 3D content on to an HTML5 canvas element in the same way OpenGL would draw 3D content on to a native application UI. What happens if you want to mess with the canvas node?

TL;DR

Let’s get to the point for people who don’t need background information or just want the answer immediately! In the Javascript call …

<script>
    var gameInstance = 
        UnityLoader.instantiate(
            "gameInstance", 
            "/blog/content/ProjectName/Build/BuildDir.json", 
            {onProgress: UnityProgress});
</script>

… the gameInstance variable has a Module member. And that Module object has a member called canvas. That’s the canvas DOM object. Do with it what you will.
Since the time of this writing, the sample HTML that Unity generates now calls this variable unityInstance.

var domCanvas = gameInstance.Module.canvas;

Do with that as you would any other Canvas node.

A Light Crash Course on the DOM

The canvas element is an HTML DOM node. The DOM a tree hierarchy that represents the page being viewed. When the site is first downloaded to the browser, it initially exists as the HTML source – but the DOM can be manipulated via JavaScript. Using JavaScript, DOM nodes can be:

  • New nodes can be added or destroyed.
  • Nodes can be shown or hidden.
  • Nodes can be moved.
  • Nodes can have their styles manipulated.

So to recap:

  • A Unity WebGL app exists as a JavaScript program
  • The app is able to render 3D content in the browser by using the WebGL API
  • The WebGL API operates on a canvas node.
  • a canvas node is an HTML tag.

Where is it in the HTML Source?

So, where is it? How can I do stuff with it? Well, let’s look at a common snippet of HTML/Javascript I used to embed content into WordPress. You’ll notice plenty of div elements, but where’s the canvas element?

<script src="/blog/content/WebHalt/TemplateData/UnityProgress.js"></script>
<script src="/blog/content/WebHalt/Build/UnityLoader.js"></script>
<script>var gameInstance= UnityLoader.instantiate("gameInstance", "/blog/content/WebHalt/Build/WebHalt.json", {onProgress: UnityProgress});</script>
<div class="webgl-content">
   <div id="gameInstance" style="width: 960px; height: 600px">
</div>

Well, the canvas doesn’t exist until the UnityLoader.instantiate() is called. When it’s called, it will find the DOM element that has the same id as the first parameter passed in (i.e. gameInstance). So the canvas doesn’t exist in the HTML source file, only the parent container where it will eventually be created into.

In this post where I don’t instance the app until a placeholder image is clicked, here’s what the DOM hierarchy looks like before and after the app is instanced.

Examples

Another parent location

Some examples of what can be done with access to the canvas. Really you can do anything with the canvas that you could do with any DOM node – so this is in no way comprehensive. Do you ever need to do any of these things or anything else with the canvas? Unless you’re trying to do something exotic that requires the right WebGL app and browser/DOM integration, the chances are that you don’t. But for more exotic Unity WebGL applications, now you’ll know.

Demo

Some explanations of them and a few other things below the demo.

Fullscreen

There’s nothing special you have to do with the canvas to fullscreen the app. This is because the app is built with a handy function you can call. It’s even in the sample HTML that Unity generates for you when you build a WebGL app.

gameInstance.SetFullscreen(true);

Restoring the app from the fullscreen status can be done by either calling the same function with a false parameter or by pressing the escape key.

Unloading The App

For Unity 2018, this article used to say that WebGL apps could not be unloaded after they’re created. Any attempt to delete them would spam errors indefinitely.

In Unity 2019 the canvas can now be deleted without issues.

// Delete the Canvas DOM object hosting the game.
gameInstance.Module.canvas.remove();

You will probably be left with a black box afterward, that will be the parent div (id=”gameInstance”).
This will be the same black box if you tested the reparenting demo above.

Hide and Show the Canvas

Hiding the canvas in Unity 2018 runs into the same issue as simply destroying the application. In Unity 2019 there are no issues. Simply change the hidden member for the Canvas or a parent node to true. And obviously, set it to false to make it visible again.

// Functions callbacks for the Show/Hide Container and Show/Hide Canvas buttons

function WebGLDemo_WebGLContentMinimize()
{
    // Disable the starting container div (that's been id-ed as webglcontainer)
     var cont = document.getElementById("webglcontainer");
     cont.hidden = true;
}

function WebGLDemo_WebGLContentRestore()
{
     var cont = document.getElementById("webglcontainer");
     cont.hidden = false;
}

function WebGLDemo_Hide()
{
     gameInstance.Module.canvas.hidden = true;
}

function WebGLDemo_Show()
{
     gameInstance.Module.canvas.hidden = false;
}

Modify Event Handlers

If you’re familiar with JavaScript, you know you can add event handlers to DOM objects to call callbacks on different events, such as when the mouse cursor moves over it, or when a key is pressed when the object is in focus, etc. Any handlers you add in JavaScript to the canvas shouldn’t interrupt the game’s ability to continue polling its own events.

// Functions callbacks for the Add/Rem Click buttons
// After clicking these buttons, make sure the click inside the app the test it.

function WebGLDemo_AddClick()
{
     gameInstance.Module.canvas.onclick = function(){alert("Canvas clicked on!");};
}

function WebGLDemo_RemClick()
{
     gameInstance.Module.canvas.onclick = null;
}

Resizing the Canvas

The canvas can be resized. Setting the width and the height seems to be interlinked, where modifying one (the width or the height) modifies the other to maintain the aspect ratio. Thus, I only have to modify one component of the size (in this example, the width).

// Functions callbacks for the Resize Small/Normal Click buttons

function WebGLDemo_MakeSmall()
{
     gameInstance.Module.canvas.style.width = "600px";
}

function WebGLDemo_MakeNormal()
{
     gameInstance.Module.canvas.style.width = "960px";
}

The parent container still maintains its size—something you have to address.

Reparenting the Canvas

Simple enough, reparent the canvas with the usual DOM node reparenting function.

// Functions callbacks for the (Re)/Reparent Click buttons

function WebGLDemo_Reparent()
{
	var newcont = document.getElementById("otherparent");
	newcont.appendChild(gameInstance.Module.canvas);
}

function WebGLDemo_Rereparent()
{
	var newcont = document.getElementById("gameInstance");
	newcont.appendChild(gameInstance.Module.canvas);
}

Restyling the Canvas

The DOM node style can be changed like anything else. Just keep in mind that because your app rendering is taking up the interior of the canvas, you won’t be able to see those effects. But, things that are visible outside the object’s bounds (such as drop shadows) are visible.

// Functions callbacks for the shadow restyling buttons

function WebGLDemo_StyleShadow()
{
     gameInstance.Module.canvas.style.boxShadow = "10px 10px 8px 10px #888888"
}

function WebGLDemo_StyleNoShadow()
{
     gameInstance.Module.canvas.style.boxShadow = "";
}

In Conclusion

I might have gotten a little gratuitous with the number of examples, but you get the idea!

Information on the sample WebGL app can be found here.
Built with Unity 2018.3.8f1
Authored and tested in Chrome.
– Stay strong, code on. William Leu

Explore more articles here.
Explore more articles about Unity WebGL here.