iOS Safari Gotchas for Web Games

Updated June 2026
iOS Safari is the most important and most frustrating browser for mobile web game development. It reaches every iPhone and iPad user (since all iOS browsers use the WebKit engine), but it has specific quirks around audio, memory, fullscreen, viewport behavior, and storage that trip up developers who only test on Chrome. This guide documents every major gotcha and its workaround.

Audio Requires a User Gesture, Every Time

Safari on iOS suspends the AudioContext until a user gesture (tap, click, or keyboard event) occurs. If you create an AudioContext during page load, its state is "suspended" and no audio will play until you call audioCtx.resume() inside a user-gesture event handler. This is not a bug, it is a deliberate policy to prevent websites from autoplaying sounds.

The gesture requirement is per-AudioContext, not per-page. Once you resume a context, it stays active for the remainder of the session. The standard pattern is a "Tap to Play" splash screen whose tap handler resumes the AudioContext. Some developers try to silently resume the context on the first incidental tap (like tapping a menu button), which also works as long as the resume call is directly inside the event handler, not in a setTimeout or Promise callback.

A secondary gotcha is that Safari on iOS limits the number of simultaneous audio sources more aggressively than Chrome. If your game plays many overlapping sound effects, you may hear some effects cut off or fail to play. Use a sound pool pattern: maintain a fixed number of AudioBufferSourceNodes (8 to 16 is usually sufficient) and recycle them round-robin. Prioritize important sounds (player actions, warnings) over ambient effects.

Memory Limits and Tab Crashes

iOS Safari enforces stricter memory limits than Chrome on Android. The exact limit depends on the device model and the amount of free RAM, but as a practical guideline, tabs using more than 300 to 400 MB on older iPhones (iPhone 8, SE 2nd gen) will often be killed by the OS without warning. The user sees the tab reload as if they navigated away and came back. On newer iPhones with 6 GB of RAM, the threshold is higher but still present.

GPU memory is a separate concern. Each WebGL texture consumes GPU memory proportional to its uncompressed size, not its file size. A 2048x2048 RGBA texture is 16 MB of GPU memory. Safari has been observed to crash or produce rendering corruption when total GPU memory usage exceeds approximately 256 MB on older devices. Keep texture atlases at 1024x1024 or smaller on mobile, and count your total texture memory budget carefully.

A specific memory leak in iOS Safari occurs when resizing a canvas that has a WebGL context. Each resize allocates a new backing store, and under certain conditions the old backing store is not freed promptly. If your game resizes the canvas frequently (for example, on every orientation change or address bar transition), memory can accumulate over the course of a play session until the tab crashes. The workaround is to debounce resize operations and, if possible, set the canvas size once during initialization and use CSS scaling to handle viewport changes.

Monitor your game's memory usage during development using Safari's Web Inspector on macOS. The Timelines tab shows memory allocation over time, and the Memory tab can take heap snapshots. Test with extended play sessions, not just quick functional tests, because memory leaks often only manifest after minutes of continuous use.

No Fullscreen API in the Browser

Safari on iOS does not support the Fullscreen API (element.requestFullscreen()) when running in the browser. The API simply does not exist. This means you cannot programmatically hide the browser UI to give your game more screen space. Chrome on Android supports the Fullscreen API and lets games take over the entire screen.

The workaround on iOS is to make your game a PWA. When a user adds your game to their home screen and launches it from there, it runs in standalone mode without the Safari address bar or navigation buttons. This is effectively fullscreen, with only the status bar still visible (and even that can be hidden by setting "display": "fullscreen" in the Web App Manifest, though this is not always reliable).

For the in-browser experience, design your game to work well within the reduced viewport that includes the address bar. The address bar on Safari collapses when the user scrolls down on a normal webpage, but in a game with a fixed canvas, there is no scroll to trigger the collapse. One technique is to have a taller page that scrolls behind the canvas, allowing the address bar to collapse naturally when the user scrolls during initial load. Another approach is to accept the address bar's presence and design your UI with sufficient margin at the top and bottom.

Viewport Height Is Complicated

The vh unit in CSS on iOS Safari is based on the viewport height with the address bar in its smallest state (collapsed). This means that 100vh is larger than the visible area when the address bar is fully visible. If you set your game container to height: 100vh, it will extend behind the address bar, clipping the bottom of your game.

Apple introduced the dvh (dynamic viewport height) unit to solve this problem. 100dvh always equals the currently visible viewport height, updating as the address bar appears and disappears. Use dvh for your game container's height. If you need to support older iOS versions that do not recognize dvh, fall back to reading window.innerHeight in JavaScript on each resize event and setting the container height explicitly.

The svh (small viewport height) unit corresponds to the viewport with the address bar fully expanded, which is the smallest available height. Using 100svh ensures your game never extends behind the address bar, but it also means the game does not fill the screen when the bar collapses. For most games, dvh is the best option because it maximizes screen usage while always showing the full game content.

Touch Events and Gestures

Safari on iOS has system gestures that can interfere with game input. Swiping from the bottom edge of the screen opens the app switcher or the home screen. Swiping from the top opens the notification center or control center. Swiping from the left edge triggers a page-back navigation. These system gestures take priority over your game's touch handling and cannot be disabled from JavaScript.

In standalone PWA mode, some of these gestures are less aggressive. The edge swipe for page-back is eliminated because there is no browser navigation stack. But the bottom and top edge swipes for system functions remain. Design your game controls to avoid the screen edges, particularly the bottom 20 pixels and the top 44 pixels (the status bar area). Place virtual buttons and interactive elements inset from the edges by at least 20 pixels on all sides.

Safari on iOS fires touch events slightly differently than Chrome on Android in some edge cases. The touchcancel event fires when Safari takes over a touch for a system gesture, and your game must handle this by treating it as a finger-up event. If you do not handle touchcancel, the game can get stuck in a "finger down" state after the user accidentally triggers a system gesture, leading to input bugs.

Double-tap to zoom is another consideration. Even with user-scalable=no in the viewport meta tag, Safari may still interpret a double-tap in certain situations. Adding touch-action: manipulation to your game container CSS reliably disables double-tap zoom and also eliminates the 300ms tap delay that was historically present on mobile browsers.

WebGL Context Loss

Safari on iOS can lose the WebGL context when the system is under memory pressure, when the user switches to another app and back, or when the tab is in the background for an extended period. A lost context means all your textures, shaders, and buffers are destroyed. The game renders a blank canvas until you handle the restoration.

Listen for the webglcontextlost and webglcontextrestored events on your canvas. When the context is lost, call event.preventDefault() to signal that you will handle restoration. Stop your game loop and show a loading indicator. When the context is restored, recreate all your WebGL resources (compile shaders, upload textures, rebuild vertex buffers) and restart the game loop.

Most game engines (Phaser, PixiJS, Three.js, Babylon.js) handle context loss automatically, but the restoration process still causes a visible interruption. On Chrome on Android, context loss is much rarer. If you are seeing it frequently on iOS, it usually means your game is using too much GPU memory and the system is reclaiming resources to stay within budget.

Storage Eviction and Partitioning

Safari on iOS applies Intelligent Tracking Prevention (ITP) rules that can affect storage for web games. If a user does not visit your game's domain for seven days, Safari may delete all cookies, localStorage, IndexedDB, and service worker caches. This means a player who comes back after a week might find their save data gone.

PWAs installed to the home screen are exempt from this seven-day eviction policy, which is one more reason to encourage players to install your game. For browser-based play, the mitigation is to offer cloud save functionality through a server-side backend, or to let players export their save data as a downloadable file that they can reimport later.

Another storage quirk: each home screen PWA instance gets its own separate storage partition. Data saved in Safari does not carry over to the home screen version of the same game, and vice versa. If a player accumulates progress in the browser and then installs the game to their home screen, they start fresh unless you provide a transfer mechanism (account login, save code, file export).

CSS and Rendering Differences

Safari uses a different rendering engine than Chrome, and CSS that works perfectly on Chrome may behave unexpectedly on Safari. For games, the most common issues involve CSS transforms, opacity, and compositing. Safari is more aggressive about creating GPU-composited layers, which can cause unexpected flickering or z-ordering issues with overlaid HTML elements.

If you overlay HTML UI elements on top of a canvas, Safari may render them in the wrong order or clip them unexpectedly. Adding transform: translateZ(0) or will-change: transform to the overlaid elements forces Safari to treat them as separate compositing layers and usually fixes z-ordering issues. Be sparing with these hints because each composited layer uses GPU memory.

Safari also has a long-standing behavior where elements with position: fixed do not stay fixed during momentum scrolling. If your game uses HTML elements positioned outside the canvas (overlay menus, chat panels), they may jump or jitter during scroll events. The most reliable fix is to make your game container take over the full viewport with overflow: hidden on the body, preventing any scrolling from occurring.

Key Takeaway

iOS Safari is both the gatekeeper and the challenge of mobile web game development. Audio needs a user gesture to unlock, memory limits are strict, fullscreen only works as a PWA, viewport height changes dynamically, edge gestures interfere with controls, and storage can be evicted after seven days of inactivity. Every one of these has a workaround, but you have to know about them and test for them specifically.