Thursday, September 29, 2011

The state of the Javascript Fullscreen API

I gave the javascript Fullscreen API a shot tonight, something I've been meaning to do for a while. To test, I went and added a simple fullscreen button to my Quake 3 demo. I've posted the current results online (Webkit only for the moment, sorry!) so that you can play with it if you want, but don't go expecting too much just yet.

The good: It does indeed switch your browser to fullscreen mode and isolate the given element.

The bad: Pretty much nothing else works yet.

[UPDATE! nornagon has addressed pretty much all of these issues in the comments! I've also updated the Q3 demo to reflect his advice. See below.]



So, first off I noticed immediately that once in fullscreen your keyboard basically stops responding. You can click and drag your viewport around with the mouse like normal, and interestingly enough you can jump (space), but WASD stop responding, so you can't move. According to the spec, there should be two different fullscreen calls: "requestFullScreen" and "requestFullScreenWithKeys". One would imagine that  the "withKeys" variant would give us our movement keys back, but in this case Chrome complains that the function doesn't exist. Hrm... This won't be much use to me if I can't control my games!

Secondly, once I've gone fullscreen the canvas size stays the same as when it was windowed. I would ideally like to have my WebGL canvas take up the entire screen when in fullscreen. Okay, that shouldn't be an issue! An "onFullScreenChanged" event fires when the screen goes to fullscreen and back, so I can utilize that to set my canvas size to that of the document when in fullscreen mode and put it back when in windowed mode. But this leaves us with a couple of problems:
  • The event fires both when entering and leaving fullscreen, but doesn't tell us which. Furthermore, I'm not entirely sure how to detect which mode I'm in at all! There are CSS Pseudo classes that should be associated with those states, but how would I query it from code?
  • Even if I could detect which was which, apparently this event fires before the document is actually resized, so querying document.width/height when going fullscreen with actually give you the size of the window just before the transition, which is less than useful. Certainly I could add another listener to the document when I get the FullScreenChanged event to see when it's size changes and respond accordingly (if I can tell that I'm fullscreen, see point 1), but that feels clunky. Why would I ever want to know what size the document was on an event like this?
So, anyway, I'd love to do something like this:

canvas.onwebkitfullscreenchange = function(event) {
    if(canHazFullscreenz) { // How do I know which?
        canvas.width = document.width; // When does this update?
        canvas.height = document.height;
    } else {
        canvas.width = 854;
        canvas.height = 480;
    }
    gl.viewport(0, 0, canvas.width, canvas.height);
    mat4.perspective(45.0, canvas.width/canvas.height, 1.0, 4096.0, projectionMat);
};

But I don't think there's enough info exposed to actually do it yet.

Ah well, I'm sure this feature will work itself out eventually. I know that there's some discussion about improving it already, so hopefully it sees some love soon. It's certainly not game-ready right now, though.

(Testing done with Chrome 16.0.894.0 canary on OSX Lion. Your mileage may vary.)

Follow Up:


Okay, so within hours of my posting this nornagon (aka: Jeremy Apthorp from Google) corrected pretty much everything from my post in the comments. But I figure that if I had a tough time figuring it out then others will too, so here's the working code.

To go fullscreen with keyboard support in Webkit, you use:

canvas.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);


This goes fullscreen, but doesn't actually resize your canvas (as I mentioned before). At this point, there are a couple of things that you can do about that. The simplest is to simply add a stylesheet:

canvas:-webkit-full-screen {
    width: 100%;
    height: 100%;
}

Which will make the canvas fill the screen, but will not resize it. This is roughly equivalent to choosing to play a game at 800x600 on a 1024x768 monitor, in that it won't look as good but you still get the fullscreen experience and it will perform better as well. This may or may not be what you are looking for, and it probably depends on the scenario. If you want the canvas to match the screen resolution, though. there's a tiny bit more work to be done.

// Handle fullscreen transition
canvas.onwebkitfullscreenchange = function() {
    if(document.webkitIsFullScreen) {
        canvas.width = screen.width;
        canvas.height = screen.height;
    } else {
        canvas.width = 854; // These are your original windowed size
        canvas.height = 480;
    }
    // Need to update the WebGL viewport.
    gl.viewport(0, 0, canvas.width, canvas.height);
    mat4.perspective(45.0, canvas.width/canvas.height, 1.0, 4096.0, projectionMat);
};

This is the code I posted earlier, but functional this time around. Note that what I said about the document size reporting wrong in this function is still true, so you can't rely on document.width/height in this case! Here I'm using screen.width and screen.height, which report the full dimensions of your monitor. That may not be the best numbers to use, but it seems to be working for me right now.

And with that we now have fullscreen that functions like you would expect! Yay! Still don't have Firefox support in the demo, because I can't find a version on the Mac that actually supports it (am I missing something?) Otherwise, things are much better than I originally thought. They just need better documentation. :)