JavaScript Fullscreen API Table
| Piece | What it does | Field note |
|---|---|---|
element.requestFullscreen() | Go fullscreen | Returns a promise - MUST run inside a user gesture (click/tap/key) or the browser rejects it |
document.exitFullscreen() | Leave fullscreen | Document-level on purpose - whoever is fullscreen, the exit belongs to the page |
document.fullscreenElement | Who is fullscreen | The element or null - the single state source; per-element boolean flags go stale |
document.fullscreenEnabled | Is it possible here | false in iframes without permission or DRM-restricted contexts - check before offering the button |
fullscreenchange | The transition event | Fires on ENTER and EXIT (including Escape/system exits) - layout must react here, async after the request |
fullscreenerror | The rejection event | Gesture timeout, iframe permission, mid-transition - the event twin of the rejected promise |
video.webkitEnterFullscreen() | The iPhone path | iOS Safari never shipped standard element fullscreen - prefixed VIDEO-only is what iPhones actually run |
Escape exits, always | The browser owns the key | Unblockable by design - react via fullscreenchange (pause video, save state), never fight it |
The Fullscreen API takes one element - a video, a canvas, a map - and stretches it over the entire screen: element.requestFullscreen() in, document.exitFullscreen() out, document.fullscreenElement tells you who is in. It is the video-player button, the game focus mode and the kiosk layout, all with one contract.
Bottom line: fullscreen is GATED on a user gesture - requestFullscreen() outside a click, keypress or touch is rejected by design, which is why the button lives in your UI and not in your init code. The state check is document.fullscreenElement (null = not fullscreen), not a boolean on the element, and the transition is asynchronous - wait for the fullscreenchange event before measuring sizes.
The honest part: iOS Safari - still a major mobile share - never shipped the standard API for arbitrary elements; videos there use webkitEnterFullscreen() on the VIDEO element itself, and webkit-prefixed variants cover older Android and Safari. Cross-browser fullscreen code is one request wrapped in feature checks, or it silently does nothing for a quarter of your users.
How to use
- Enter fullscreen on click: button.addEventListener('click', () => stage.requestFullscreen()) - the gesture requirement is non-negotiable, so always trigger from user input.
- Toggle safely: document.fullscreenElement ? document.exitFullscreen() : stage.requestFullscreen() - one state source, no stale boolean flags.
- React to the transition: document.addEventListener('fullscreenchange', () => resize(stage)) - layout changes ASYNC after the request resolves; measuring immediately reads the old viewport.
- Handle rejection: requestFullscreen().catch(() => showFallback()) - iframe without allowfullscreen, gesture timeout and Escape-mid-request all reject; the catch is where your non-fullscreen UX lives.
- Cover iOS Safari: if (video.webkitEnterFullscreen) video.webkitEnterFullscreen(); else if (stage.requestFullscreen) stage.requestFullscreen(); - prefixed video-only fullscreen beats nothing on iPhones.
Frequently asked questions
Why does requestFullscreen throw even though the user clicked?
The gesture window is short and strict: the call must happen in the same task as the click (a setTimeout or awaited fetch between click and request loses the gesture), iframes need the allowfullscreen attribute and an allow=fullscreen permission, and some browsers refuse while another fullscreen transition is still settling. The rejection arrives as a rejected promise (or fullscreenerror), so every production player wires the catch to a visible fallback instead of an unhandled rejection in the console.
How do I know if fullscreen is currently active?
document.fullscreenElement - the element currently filling the screen, or null. Per-element flags (element fullscreen or not) go stale because the EXIT can come from outside your code: the Escape key, the browser UI, or the system leaving fullscreen all fire fullscreenchange with fullscreenElement becoming null. Listen to the document-level change event and read fullscreenElement as the single source of truth; document.fullscreenEnabled separately tells you whether fullscreen is POSSIBLE here at all (DRM restrictions, iframe permissions).
Does Escape exit fullscreen, and can I block it?
Escape exits, and you cannot block it - the browser owns that key in fullscreen by design, along with the system gesture on mobile. Your code reacts via fullscreenchange (restore your layout, persist state) rather than preventing the exit. The same applies to window focus loss and screen locks: treat any fullscreen exit as a pause signal for video and games, which is exactly what every polished player does.
What is the cross-browser state of the API in practice?
Standard requestFullscreen/exitFullscreen/fullscreenElement is solid on desktop Chrome, Edge, Firefox and Safari, plus Android Chrome. The gap is iPhone Safari: no standard element fullscreen - videos use the webkit-prefixed method on the VIDEO element (webkitEnterFullscreen, video-only, native controls) and webkitEnterFullScreen spelling variants trap the unwary. Feature-detect in order: standard, webkit video, fallback message - and remember canvas and divs simply cannot go fullscreen on iPhone, so design the mobile layout to not need it.