safe-area-inset values on iOS11

With the iPhone X’s notch came viewport-fit=cover and safe-area-inset, as explained here. It turns out that safe-area-inset is 0 on iOS11 devices that are not the iPhone X. This may sound logical, but I wonder if it is. Also, the value remains static, even when you zoom in.

Note: testing for this article was done exclusively on Apple’s simulator.

To recap briefly:

viewport-fit

Let’s treat viewport-fit=cover first. When applied on the iPhone X, your sites now stretches into the space below the notch, as advertised. When applied on any other device with iOS11, nothing happens. That’s logical: the viewport is already stretched to its maximum and there is no notch to avoid or stretch under.

In other words, viewport-fit=cover can be added to any site and will fire only when applicable. Keep that in mind.

The safe area

safe-area-inset should be added as a padding (or, I suppose, a margin) to elements or the entire page. Its value on the iPhone X, in case you’re wondering, is 44px. This value could conceivably be different on future models where the notch is larger or smaller, so using a constant that may change from model to model is a good idea.

But what is its value on iOS11 devices that are not the iPhone X and have no notch? It turns out it’s 0px. This may sound logical as well, since there is no notch and thus no safe area, but is it?

My problem is the following. Suppose I have this:

element {
	padding-left: 10px;
	padding-left: constant(safe-area-inset-left);
}

What I want to do here is give the element a padding-left of 10px, except when a notch is present, then I want to give it a pading-left equal to the safe area (44px). This works absolutely fine on the iPhone X and in non-iOS browsers. In the former the initial 10px values is overwritten by the safe area, while the latter don’t understand the safe area and ignore the second rule.

Problem is: on iOS11 devices other than the iPhone X this misfires and gives the element a padding-left of 0. Thus, safe-area-inset fires even when it’s not applicable. I do not find this logical at all. As far as I can see, safe-area-inset should simply be absent when there is no safe area to describe. And 0 is not the same as absent.

As far as I’m concerned Apple should remove safe-area-inset entirely from devices that do not need it. Thus we web developers do not need to worry about the notch. We write a tiny bit of CSS for the notch, and can rest assured that the CSS will not fire when it’s absent.

The official post notes that you should use the following instead, but also notes that max() is not supported by the current Safari/iOS version, which makes the advice a bit pointless:

element {
	padding-left: max(10px,constant(safe-area-inset-left));
}

So they kind-of admit there might be a problem, but offer an as-yet-unavailable solution. Also, as far as I’m concerned this tip-toes around the fundamental problem of having a safe area of 0 where none is needed.

Zoom

There’s another problem as well: safe-area-inset is not adjusted when the user zooms, even though, at high zoom levels, the safe area becomes comically large. Even when I’m zoomed in to the maximum level on an iPhone X, the safe area is still 44px, though that now means about one-third of the screen.

How safe-area-inset misfires on high zoom levels: it gives way too much padding-left

I can understand why Apple did this. If safe-area-inset would become zoom-dependent, the browser would have to run re-layouts every time the user zooms, changing the calculated padding-left on every applicable element. This is likely to be a costly operation.

Still, the conclusion must be that safe-area-inset also misfires whenever the user zooms in.

Notch detection

So we have to write a notch detection script. Fortunately it’s quite simple: create a test element, apply the safe-area-inset and see if its value is larger than 0. If so, a notch is present.

function hasNotch() {
	if (CSS.supports('padding-left: constant(safe-area-inset-left)')) {
		var div = document.createElement('div');
		div.style.paddingLeft = 'constant(safe-area-inset-left)';
		document.body.appendChild(div);
		var calculatedPadding =  parseInt(window.getComputedStyle(div).paddingLeft);
		document.body.removeChild(div);
		if (calculatedPadding > 0) {
			return true;
		} 
	}
	return false;
}

Still, I would argue that the very need for such a script means safe-area-inset has not been implemented quite properly.

This is the blog of Peter-Paul Koch, web developer, consultant, and trainer. You can also follow him on Twitter.
Atom RSS

Categories:

Monthlies: