Desktop media query bugs 1: rounding and width

I recently tested media queries in both mobile and desktop browsers, and found that the desktop browsers are making several serious mistakes in their implementation that threaten to pull apart the mobile and desktop worlds.

I feel that desktop browser makers have focused on desktop use cases too narrowly and failed to see that these changes introduce deliberate incompatibilities between desktop browsers and mobile browsers. It’s web developers who have to solve this mess, and it’s likely they can only do so by using browser detects.

I feel the desktop browsers should defer to their mobile cousins in viewport matters, since mobile is the more complicated use case and a de-facto standard is emerging. Their recent actions threaten this process. This blog post and the next ask them to reconsider several ideas.

I identified three distinct problems in the desktop browsers:

  1. The values that the media queries react to are floating points, while the JavaScript property values they’re slaved to are integers. This threatens the equivalency of media query-based and JavaScript-based responsive design.
  2. The width and height media queries are no longer slaved to document.documentElement.clientWidth/Height. Instead, they take their cues from window.innerWidth/Height. This means desktop browsers now treat these media queries differently than mobile browsers.
  3. The device-pixel-ratio changes when the user zooms. This causes a lot of trouble because we have to port all kinds of mobile concepts to the desktop environment, where they don’t make sense. Also, I feel the use cases for this change are insufficiently defined.

This article treats the first two problems, which are relatively simple. A separate article will treat the very complicated third problem.

Definitions

Before continuing to the width and height problems, a brief summary of some aspects of the mobile viewport. Note that the definitions below hold in nearly all mobile browsers, and form a de-facto standard.

Layout viewport
The viewport relative to which CSS declarations such as width: 20% are calculated. Equal to the CSS initial containing block.
On desktop the layout viewport is as wide as the browser window. On mobile it has a default value of between 768 and 1024 pixels, and web developers can set its width by means of the meta viewport.
Visual viewport
The current size of the part of the site that’s shown on the device screen. Can change wildly as the user zooms in and out. Is not very important in the current discussion, but is referred to a few times.
document.documentElement.clientWidth/Height
Mobile: give the current width and height of the layout viewport.
Desktop: give the current width and height of the viewport including scrollbars.
window.innerWidth/Height
Mobile: give the current width and height of the visual viewport.
Desktop: give the current width and height of the viewport excluding scrollbars.
width and height media queries
Are slaved to document.documentElement.clientWidth in ALL mobile browsers, even when its value does not make sense.

The equivalency principle

Many media queries have their JavaScript equivalents. That is, they are slaved to the values of certain JavaScript properties, allowing web developers to switch from CSS to JavaScript if their sites need it.

JavaScript is sometimes a better tool than media queries; for instance when you have to decide whether to include those Twitter or Facebook widgets or not. Media queries don’t help here, since they can at most hide the widgets. JavaScript is necessary for not loading them in the first place. Read out the layout viewport width, and load the widgets only if that width is above a certain value.

Therefore, the two following expressions MUST be exactly equivalent, and in fact are in ALL mobile browsers:

@media all and (max-width: 533px) {
	// styles
}

if (document.documentElement.clientWidth <= 533) {
	// styles
}

It turns out desktop browsers destroy this equivalency by using floating points for media queries, and by redefining the JavaScript equivalent of the width media query.

The rounding bug

The rounding bug is simple. It occurs only when you zoom desktop browsers in or out, but in the next article we’ll see that this is becoming an important use case.

Take a (layout) viewport with a width of 533.333333, or a similar non-integer number. Zooming in can give this kind of values quite easily, and desktop browsers handle them badly.

document.documentElement.clientWidth/Height, and, in fact, all JavaScript properties that expose pixel widths of elements or viewports, are integers. The 533.333333 is rounded down to 533. Everybody knows that.

However, media queries use floating points. The 533.333333 is not rounded down, and the value the media query uses is not equal to the value of the JavaScript property. This violates the equivalency principle.

The rounding bug affects the following media queries; in all cases when the user zooms:

  1. width and height in all browsers except for the old Presto-based Operas.
  2. aspect-ratio in all browsers except for IE and the old Presto-based Operas.
  3. device-aspect-ratio in Firefox.

The solution is simple: just use integers.

Equivalency of the width and height media queries

The width media query is one of the two cornerstones of responsive design. (The other one is the meta viewport tag.) Which JavaScript property is it equivalent to?

Well, that depends on your device type:

  1. ALL 40 mobile browsers I tested slave the width and height media queries to document.documentElement.clientWidth/Height.
  2. All desktop browsers except for Safari slave the width and height media queries to window.innerWidth/Height. Safari sides with the mobile browsers.

Test the width media query here. If you’re on Mac, first set the scrollbars to always appear (System Preferences -> General -> Show scroll bars always). Compare Safari with any other browser.

Since width is THE vital media query for responsive design it should not be slaved to one property on mobile, and another one on desktop.

Mobile browsers: why?

Why do mobile browsers use document.documentElement.clientWidth/Height? Because these properties contain the information they need.

On mobile, document.documentElement.clientWidth/Height give the dimensions of the layout viewport, while window.innerWidth/Height give the dimensions of the visual viewport.

The width and height media query give information about the layout viewport, and thus they must be slaved to document.documentElement.clientWidth/Height. Simple.

This behaviour is very old. As far as I know Nokia and BlackBerry had already implemented it when the first iPhone appeared. Apple copied it, and the later mobile browser vendors followed suit (sometimes after a bit of prodding by yours truly). Only Firefox still doesn’t expose the visual viewport dimensions correctly, but that is supposed to change in 27 or 28 (I forget which).

Desktop browsers: why?

Why do desktop browsers use window.innerWidth/Height? Because of the scrollbar. With this definition they exclude the width of the scrollbar from their calculations.

If desktop browsers would use document.documentElement.clientWidth/Height, and a responsive design would hit a breakpoint, part of the page content might show up under the vertical scrollbar, causing a horizontal one to appear. Desktop browsers want to avoid that.

IE and Firefox have been doing this forever. The change in Blink, which affects Chrome and Opera, is recent, though. The Yandex browser, which uses Chromium 28, still uses document.documentElement.clientWidth/Height as equivalent for the width and height media queries.

Safari, too, sticks with the old definition. But Safari has it easy, since it’s Mac-only. On Mac, the default setting hides scrollbars as much as possible and thus no space is left open for them. Therefore document.documentElement.clientWidth/Height and window.innerWidth/Height give the same results. (I manually enabled scrollbars for my tests.)

The difference

Despite their behaviour being understandable, desktop browsers have broken compatibility with their mobile cousins. How bad is that for web developers who use the JavaScript equivalent to the width media query?

A web developer used to mobile will use document.documentElement.clientWidth on desktop, too, and will thus be about 12-16px off (except on a Mac). That can be annoying sometimes, but it’s hardly earth-shattering. If this were the only scenario I’d be inclined to let it rest.

What I’m afraid of is the opposite scenario. A desktop web developer uses window.innerWidth but is quite unaware that that means something totally different on mobile. His script will misfire dramatically on mobile and severe breakage will occur.

Solving the conundrum

How do we solve the issue? I see three options here:

  1. Accept the incompatibilities between desktop and mobile, and forget about the One Web.
  2. Change desktop behaviour to match the mobile behaviour, and accept the fact that responsive designs might occasionally stretch underneath the vertical scrollbar.
  3. Change the desktop behaviour AND switch the definitions of document.documentElement.clientWidth and window.innerWidth on desktop.

I reject option 1. The desktop browsers are in the wrong here, since they insert a serious incompatibility in the handling of the most important media query there is. Also, this option severely hampers the freedom of web developers to switch to a JavaScript solution if they so desire.

Option 3 sounds great in theory, but changing the definition of long-established JavaScript properties is not something to be done lightly. Older sites that depend on one property or the other will certainly break.

That leaves option 2: accepting the fact that on desktop responsive designs might occasionally stretch underneath the vertical scrollbar. Sure, some sites will suffer, but web developers can easily fix that by changing some breakpoints slightly.

I’m not happy with option 2, but I see it as the least intrusive. Therefore Trident, Gecko, and Blink should be changed so that the width and height media queries are slaved to document.documentElement.clientWidth.

Conclusion

We have seen the following:

That concludes the simple problems. The really tricky one is the porting of the mobile device-pixel-ratio concept to desktop without taking stock of the consequences. We’ll treat that in the next article.

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

If you like this blog, why not donate a little bit of money to help me pay my bills?

Categories: