Skip to main content

Safe Areas

On many phones, parts of the display are covered: by a notch or a camera cutout, by rounded corners, or by the home indicator along the bottom edge. Browsers describe the rest of the display, the safe area, with four CSS environment variables: safe-area-inset-top, safe-area-inset-right, safe-area-inset-bottom and safe-area-inset-left, the distance in CSS pixels from each edge of the page that may be covered. An in-canvas interface knows nothing of them, so read them in code and keep the essential parts of the interface inside them.

Two phones in landscape, each with a camera cutout at the left and a home indicator at the bottom. On the first phone, the corner elements of the HUD are partly covered. On the second, they sit inside the safe area

Letting the Page Reach the Edges​

Unless a page asks otherwise, these browsers keep it clear of the covered areas themselves, for example by leaving bars at the sides of the display in landscape, and report insets of 0. To use the whole display, a page opts in with viewport-fit=cover in its viewport meta tag:

<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">

Applications published from the Editor include it. Add it to the pages you write yourself.

Reading the Insets​

The environment variables are only available to CSS, so read them through an element padded with them:

// Read the safe-area insets, in CSS pixels, from an element padded with them
function readSafeAreaInsets() {
const probe = document.createElement('div');
probe.style.cssText = 'position: fixed; visibility: hidden; pointer-events: none; ' +
'padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);';
document.body.appendChild(probe);
const style = getComputedStyle(probe);
const insets = {
top: parseFloat(style.paddingTop),
right: parseFloat(style.paddingRight),
bottom: parseFloat(style.paddingBottom),
left: parseFloat(style.paddingLeft)
};
probe.remove();
return insets;
}

The insets change when the phone is turned, so read them again whenever the canvas changes size.

Keeping the Interface Inside​

Put the essential parts of the interface, such as scores, buttons and menus, below a group element that fills the screen, and give the group margins equal to the insets. Anchored to the group rather than to the screen, they stay inside the safe area, while backgrounds and decorations anchored to the screen still reach its edges.

The insets are in CSS pixels, and margins are in screen units. On a screen-space screen, one CSS pixel is canvas.width / canvas.clientWidth pixels of the drawing buffer, and one screen unit is scale pixels of the drawing buffer (see Screens). These examples assume a canvas that fills the page, as the insets are measured from the edges of the page:

// A group that fills the screen, for everything that must stay in the safe area
const safeArea = new pc.Entity('safe area');
safeArea.addComponent('element', {
type: pc.ELEMENTTYPE_GROUP,
anchor: [0, 0, 1, 1],
margin: [0, 0, 0, 0]
});
screen.addChild(safeArea);

// Anchored to the group, the pause button stays inside the safe area
const pauseButton = new pc.Entity('pause button');
pauseButton.addComponent('element', {
type: pc.ELEMENTTYPE_IMAGE,
anchor: [1, 1, 1, 1],
pivot: [1, 1],
width: 80,
height: 80,
useInput: true
});
safeArea.addChild(pauseButton);
pauseButton.setLocalPosition(-20, -20, 0);

const applySafeArea = () => {
const insets = readSafeAreaInsets();
const canvas = app.graphicsDevice.canvas;
const units = (canvas.width / canvas.clientWidth) / screen.screen.scale;
safeArea.element.margin = new pc.Vec4(
insets.left * units,
insets.bottom * units,
insets.right * units,
insets.top * units
);
};
applySafeArea();
app.graphicsDevice.on('resizecanvas', () => applySafeArea());

An interface built with HTML and CSS can use the environment variables directly, for example with padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left) on its root element.

Testing Without a Phone​

Desktop browsers report insets of 0. To check a layout on the desktop, make readSafeAreaInsets return test values instead of the real ones, such as those of a phone in landscape with a camera cutout at the left:

// Test values in place of the real insets: remove before publishing
function readSafeAreaInsets() {
return { top: 0, right: 47, bottom: 21, left: 47 };
}

To test with real insets without a phone, use a device simulator, such as the iOS Simulator that comes with Xcode.

See Also​

  • Screens - Scaling an interface and adapting it to any canvas
  • HTML and CSS - Interfaces built with the DOM
  • Mobile UI Safe Areas - Editor project with a reusable safe area script
  • env() - The CSS environment variables, on MDN