Responsive Layouts for Mobile Web Games
Responsive design for websites relies on flexible containers, percentage widths, and media queries. Games do not work that way. A chess board must be square. A platformer level has fixed geometry. A card game's hand needs to fit on screen regardless of device. The techniques below solve these problems with approaches specific to canvas-based games, where you control every pixel of the rendering output.
Define a Reference Resolution
Pick a single coordinate space for all your game logic, art assets, and UI positioning. This is your reference resolution. Common choices are 1920x1080 for landscape games and 1080x1920 for portrait games. All positions, sizes, and distances in your game code use this coordinate system, regardless of what the actual screen size is.
The reference resolution is not the pixel resolution of the canvas. It is a virtual coordinate system. When you say "place this button at (960, 540)," you mean the center of a 1920x1080 space. The actual pixel position on a given device is calculated by the scaling system described in the next step. This separation between game coordinates and screen coordinates is what makes the layout work across different devices.
Choose a reference resolution that matches your art asset pipeline. If your sprite artist creates assets at 1x scale for a 1080p game, use 1920x1080. If they work at 2x, use the corresponding dimensions. Consistency between your art production resolution and your game reference resolution avoids scaling artifacts and simplifies asset management.
Scale the Canvas to Fit the Viewport
The canvas element has two sets of dimensions: its internal rendering resolution (set via canvas.width and canvas.height) and its display size on the page (set via CSS). These are independent. You can render at one resolution and display at another, and the browser handles the scaling.
Query the available viewport with window.innerWidth and window.innerHeight. Calculate the scale factor needed to fit your reference resolution into this space while maintaining the aspect ratio. The scale factor is the smaller of viewportWidth / referenceWidth and viewportHeight / referenceHeight. Multiply your reference dimensions by this factor to get the CSS display size of the canvas.
Set the canvas rendering resolution by multiplying the display size by the device pixel ratio (capped appropriately, discussed in step 4). This means a canvas displayed at 375x667 CSS pixels on an iPhone with 3x pixel ratio renders at 1125x2001 actual pixels, producing sharp output. Center the canvas in the viewport using CSS flexbox or absolute positioning with auto margins, which automatically creates letterbox or pillarbox bars if the aspect ratios do not match exactly.
When drawing to the canvas, apply a scaling transform at the beginning of each frame. In Canvas 2D, call ctx.scale(renderWidth / referenceWidth, renderHeight / referenceHeight). In WebGL, include the scale in your projection matrix. This maps your game-coordinate draw calls to the actual pixel grid without changing any game logic.
Handle Aspect Ratio Differences
Mobile screens come in many aspect ratios. An iPhone 15 is 19.5:9, a standard Android phone might be 20:9, an iPad is 4:3, and an iPad Pro is closer to 3:2. Your reference resolution has one specific ratio. You need a strategy for what to do when the screen does not match.
Letterboxing is the simplest approach: scale the game to fit within the screen and fill the remaining space with black bars (or a decorative border). Horizontal bars appear when the screen is wider than the game, and vertical bars appear when it is taller. The game content is always fully visible and undistorted. This works well for games where every pixel of the game world matters, like puzzle games and board games.
Viewport expansion is an alternative that uses the extra screen space instead of wasting it. If the screen is wider than the reference ratio, show more of the game world on the sides. If it is taller, show more on the top and bottom. This requires designing your game world with flexible edges, placing important content in the center, and ensuring that the expanded view does not reveal content that should be hidden (like off-screen enemies in a platformer).
A hybrid approach works well for many games: fix the game world to the reference resolution with letterboxing, but extend the HUD and UI into the extra space. Score displays, health bars, and inventory panels can sit in the letterbox area, using screen real estate that would otherwise be empty. This gives you a fixed game area with responsive UI placement.
Adapt for Pixel Density
Modern phones have device pixel ratios of 2x to 3x, meaning 4 to 9 physical pixels per CSS pixel. Rendering your canvas at the CSS pixel count produces a blurry result because the browser upscales it. Rendering at the full device pixel ratio produces sharp output but requires the GPU to fill 4 to 9 times more pixels, which can tank frame rates on mid-range devices.
The practical approach is to cap the effective pixel ratio. Most players cannot distinguish 2x from 3x on a phone-sized screen, especially in a moving game. Cap window.devicePixelRatio at 2 for most games. For performance-sensitive games with complex rendering, cap at 1.5. For text-heavy games where sharpness matters (visual novels, card games), allow the full device pixel ratio.
Implement this as a quality setting if your game has a settings menu. Let the player choose between "Performance" (1x DPR, maximum frame rate), "Balanced" (1.5x DPR), and "Quality" (full DPR, maximum sharpness). Detect the device's GPU tier if possible (by checking WebGL renderer strings or running a quick benchmark on first load) and set an appropriate default.
Remember that canvas resolution affects both rendering cost and memory usage. A 1920x1080 canvas at 3x DPR becomes 5760x3240, which is over 18 million pixels. At 4 bytes per pixel, the framebuffer alone consumes 70 MB of GPU memory. Two framebuffers (for double buffering) consume 140 MB. On a phone with 1 GB of available GPU memory, that is a significant chunk. Capping the pixel ratio is not just about frame rate, it is about staying within the device's memory budget.
Manage Orientation Changes
When a player rotates their phone, the viewport dimensions swap. A 375x812 portrait viewport becomes an 812x375 landscape viewport. Your game needs to handle this transition smoothly, or you need to lock to one orientation.
For games designed for a single orientation, show a rotation prompt when the player is in the wrong orientation. Detect the current orientation with window.matchMedia('(orientation: portrait)') and listen for changes. Overlay a full-screen message with a rotation icon and text like "Please rotate your device to landscape." Pause the game loop while the prompt is visible so the player does not lose progress during the transition.
For games that support both orientations, you need separate UI layouts for portrait and landscape. The game world coordinates stay the same, but HUD elements reposition. In landscape, place controls in the lower left and right. In portrait, stack controls at the bottom center. Listen for the resize event (which fires on orientation changes) and recalculate your canvas dimensions, scale factors, and UI positions.
In a standalone PWA, the Web App Manifest's orientation field locks the orientation when the game is launched from the home screen. This is the most reliable solution for single-orientation games. In the browser, the Screen Orientation API (screen.orientation.lock('landscape')) works on Android Chrome but not on Safari, so the rotation prompt remains the universal fallback.
Account for Safe Areas and Browser Chrome
Modern phones have hardware intrusions that eat into the screen area. The iPhone's notch and Dynamic Island obscure the top center. The home indicator bar on iPhones without a home button sits at the bottom. Android phones with punch-hole cameras have a small circular cutout in the screen. These areas are reported by the browser through CSS environment variables.
Add <meta name="viewport" content="viewport-fit=cover"> to your HTML to allow your content to extend behind the notch and home indicator areas. Then use env(safe-area-inset-top), env(safe-area-inset-bottom), env(safe-area-inset-left), and env(safe-area-inset-right) to inset your interactive UI elements. The game canvas itself can fill the full screen edge to edge, providing an immersive backdrop, while buttons, score displays, and menus stay within the safe area.
Read the safe area inset values from JavaScript using getComputedStyle on an element with those CSS properties set. Pass the inset values to your game's UI layout system so your canvas-drawn buttons and text respect the safe areas. Recalculate when the orientation changes, because the safe area insets are different in portrait versus landscape.
The browser address bar on mobile Safari changes the viewport height as it appears and disappears. Use the dvh (dynamic viewport height) CSS unit for your game container instead of vh. Alternatively, listen for resize events and read window.innerHeight each time, which always reflects the current available height including the address bar state. Avoid using 100vh as a fixed height, because on iOS it corresponds to the height with the address bar collapsed, causing your game to extend behind the address bar when it is visible.
Responsive game layout starts with a fixed reference resolution for your game world and scales it to fit any screen. Handle aspect ratio differences with letterboxing or viewport expansion, cap the device pixel ratio for performance, and use CSS environment variables to keep UI clear of notches and browser chrome. These six steps cover every screen size from budget phones to large tablets.