4

Can't say is it a real problem or I'm just being paranoid but this behavior of media queries is really driving me crazy for last couple of hours.

Let's consider this really simple CSS.

 body {background:yellow}

 @media (max-width:399px) {
   body {background:red}
 }

 @media (min-width:400px) {
   body {background:blue}
 }

Problem happens when width is 399.333px! (or any float-value between 399 and 400 integers)

My logic says that by using this CSS style page will never turn yellow, right? It should be red when viewport size is shorter than 400px in width and blue when it's 400px and over.

Weird behavior happens with Opera browser (I'm using 36.0 at the moment) on Windows when page is zoomed-in. I understand that viewport width is calculated using real viewport width and current zoom-level and this value should always be integer. But...

Looks like Opera doesn't round/floor/ceil that value which affects on entire page. I'm getting yellow background when Opera finds out that viewport-width is not 399px or 400px but it's 399.333px!? So none of media queries fulfills condition.

I've already tried to find an answer here and web-wide but nothing is close enough to this problem. This problem already happened to me when I was using em units so I could work around and turn them to pixels, but I can't affect user's decision about using browser's zoom feature.

Is there something I can do to prevent this or that's just the way it is?

The easiest way to simulate this behavior is hitting CTRL,+ three times and than easily move vertical slider in Object Inspector.

update:

Yes, I can fix it with "mobile/desktop first" approach by linking each media break-point to previous one but that's not part of my question. Also, default body style is here as visual aid only and changing that really doesn't solve problem.

Wh1T3h4Ck5
  • 8,399
  • 9
  • 59
  • 79
  • 3
    One way to ensure this situation doesn't arise is to simply design either mobile-first (declare the styles for small screens first, then below that put the styles for wider screens in a series of increasing `@media (min-width...` queries), or desktop-first (the reverse: begin with the styles for the widest screens, then below that a series of decreasing `@media (max-width...` queries). – Mr Lister Jan 03 '17 at 17:59
  • Is there an actual application to this problem? You *could* just remove `background:yellow`, and place `background:red` outside of its media query. – Drew Kennedy Jan 03 '17 at 18:01
  • @MrLister yeah, I was using this approach but there are a lot of css to be rewritten this way for each break-point so my idea was to write entire css on range-level in order to improve performances. It's certainly plan B. Question why Opera doesn't round this value is thing that really kills me. – Wh1T3h4Ck5 Jan 03 '17 at 18:07
  • Mobile/Desktop first is the best practise in current development practises. It's the only way you'll avoid this problem. – zsawaf Jan 03 '17 at 19:57
  • @zsawaf May you backup your claim with some relevant source, please. All I need is to be 100% sure about this and couldn't find anything in Opera's docs. Maybe I missed something. – Wh1T3h4Ck5 Jan 03 '17 at 20:23
  • @Wh1T3h4Ck5 I can't read zsawaf's mind, but I presume they mean you avoid problems with conflicting queries like `max-width:600px` and `min-width:500px` in this way. Of course it's not the answer to the question, just a workaround. That's why none of us posted an actual answer... – Mr Lister Jan 04 '17 at 07:15
  • By the way, the problem is _not_ unique to Opera. I can duplicate it in Firefox, Chrome and IE11. – Mr Lister Jan 04 '17 at 07:20

2 Answers2

2

A simple solution could be the following:

 body {background:yellow}

 @media (max-width:400px) {
   body {background:red}
 }

 @media (min-width:400px) {
   body {background:blue}
 }

The rules in the last media query will simply overwrite any parameters that exist previously, just because of the order.

That way there won't be a situation/width which isn't covered by these two media queries: Everything up to 399.9999... (whatever) fulfills the first condition, everything above 400 will meet the second condition, and if the width is exactly 400, the rules in the second media query will overwrite the previous ones due to their order.

Johannes
  • 64,305
  • 18
  • 73
  • 130
1

Similar layout

The answer to this question should be to avoid the problem altogether, and simply leave one of the media queries out, and let one media query override the other:

body {background: red;}

@media (min-width: 400px)
{
    body {background: blue;}
}

However, when you need a fundamentally different layout, this would cause a lot of additional code simply to reset one case from the other. As is the case in the following example:

.some-element {background-color: red;border-left: 30px;}

@media (max-width: 399px)
{
    .some-element {border-left: none;padding-bottom: 40px;}
}

Whereas it would be shorter and more elegant to write:

.some-element {background-color: red;}

@media (min-width: 400px)
{
    .some-element {border-left: 30px;}
}

@media (max-width: 399px)
{
    .some-element {padding-bottom: 40px;}
}

But neither of the media-queries in the last example code will take effect if the width is for instance 399.5px. Read the next part of this answer, if you still wish to write such code with perfect coverage.

Use floating-point numbers

Unfortunately the media queries for min-width and max-width values are always inclusive. A browser uses fractional pixels when it has zooming capabilities. Therefore, a simple solution to this problem is to increment your threshold pixel value of 400px with the lowest possible fraction, for instance to 400.00001px. However, the crucial question then remains, what is the lowest possible fraction?

The CSS specification does not say anything about which data types are used to store numbers:

A <number> can either be an <integer>, or it can be zero or more digits followed by a dot (.) followed by one or more digits.

But according to the answer to 'Why does Bootstrap use a 0.02px difference between screen size thresholds in its media queries?':

Indeed, cross-browser compatibility is the reason: according to Bootstrap's source, 0.02px is used "rather than 0.01px to work around a current rounding bug in Safari.

Apparently, bootstrap being a widely used framework, it seems that 0.02 would be the correct value to use in this specific case.

In your case, to get a perfect coverage of your media queries - and thereby prevent a yellow background, the solution would look like this:

body {background: yellow;}

@media (max-width:400px) {
    body {background: red;}
}

@media (min-width:400.02px) {
    body {background: blue;}
}

Use CSS4

As of CSS4 you may use intuitive operators for media queries such as >= and <= instead of min- and max-, and more importantly, additionally you may use exclusive operators such as > and < which immediately solves the problem (see here).

However, it may not be widely supported. Unfortunately, this feature is not yet on Can I Use to check browser support. You may check it yourself using this codepen. It seems to work in the latest version of Firefox.

The solution would then be as simple as:

body {background: yellow;}

@media (width <= 400px) {
    body {background: red;}
}

@media (width > 400px) {
    body {background: blue;}
}
Yeti
  • 2,647
  • 2
  • 33
  • 37
  • 2021 info : the Range syntax from Media Queries that @Yeti talk in the section **Use CSS4** in now available on Can I Use : https://caniuse.com/mdn-css_at-rules_media_range_syntax – Ben Souchet Jan 02 '22 at 22:56