Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why `1vh` CSS size resolve to different pixel size on screen

I have three div with same heights equal to 1vh`.

My problem is they appear with different pixel size on screen. Sometimes they look equal but sometimes not, specially it happens after viewport resizing. Please run the snippet to see.

.samples {
  display:flex;
}

.container{
  display:flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
  margin:10px 20px;
  height: 20vh;
  width:20vh;
}

.container div{
  background: #000;
  width:100%;
}

#set1 div{
  height:.3vh;
}

#set2 div{
  height:.7vh;
}

#set3 div{
  height:.9vh;
}

#set4 div{
  height:1.1vh;
}
<div class = "samples">

  <div class="container" id="set1" >
    <div></div>
    <div></div>
    <div></div>
  </div>

  <div class="container" id="set2" >
    <div></div>
    <div></div>
    <div></div>
  </div>

  <div class="container" id="set3" >
    <div></div>
    <div></div>
    <div></div>
  </div>
  
    <div class="container" id="set4" >
    <div></div>
    <div></div>
    <div></div>
  </div>
  
</div>
<div>
<h4>Above are four samples, each sample includes 3 identical div.</h4>
<h4>But depends on your screen size you may not see what expected</h4>
</div>

I took some screenshot for demonstrating what really happening during resize, as you can see div looks with different height despite all of them have 1vh height.

enter image description here

If I calculate 1vh manually in javascript and inject it as rounded 'px' to CSS then it works perfectly.(In snippet I have used .3, .7, .9, 1.1 vh for demonstration)

  
  const set1Height = Math.round(document.documentElement.clientHeight * .3 / 100);
  const set2Height = Math.round(document.documentElement.clientHeight * .7 / 100);
  const set3Height = Math.round(document.documentElement.clientHeight * .9 / 100);
  const set4Height = Math.round(document.documentElement.clientHeight * 1.1 / 100);
  

  document.documentElement.style.setProperty("--set1-height", `${set1Height}px`);
  document.documentElement.style.setProperty("--set2-height", `${set2Height}px`);
  document.documentElement.style.setProperty("--set3-height", `${set3Height}px`);
  document.documentElement.style.setProperty("--set4-height", `${set4Height}px`);
  
.samples {
  display:flex;
}

.container{
  display:flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
  margin:10px 20px;
  height: 20vh;
  width:20vh;
}

.container div{
  background: #000;
  width:100%;
}

#set1 div{
  height: var(--set1-height);
}

#set2 div{
  height: var(--set2-height);
}

#set3 div{
  height: var(--set3-height);
}

#set4 div{
  height: var(--set4-height);
}
<div class = "samples">

  <div class="container" id="set1" >
    <div></div>
    <div></div>
    <div></div>
  </div>

  <div class="container" id="set2" >
    <div></div>
    <div></div>
    <div></div>
  </div>

  <div class="container" id="set3" >
    <div></div>
    <div></div>
    <div></div>
  </div>
  
    <div class="container" id="set4" >
    <div></div>
    <div></div>
    <div></div>
  </div>
  
</div>
<div>
<h4>Here, after calculating heights and round them to `px` they looks identical</h4>
</div>

My first thought was rounding floating point numbers cause this but if this is the case why CSS rounds a single float number to three different numbers?

My question is why this happening and is there any pure CSS solution to safely use viewport sizes and make sure they always appear as expected?

like image 380
Makan Avatar asked Mar 10 '20 11:03

Makan


People also ask

How many pixels is 1vh?

Ex: The value of “1vh” will be 10 pixels (px), and the value of “10vh” will be 100px if the viewport is 1200px wide and 1000px high.

Does screen resolution affect CSS?

In other words, CSS resolution is the main measure that affects modern responsive UI development. But device resolution is still important. It determines how images and videos are displayed. Due to the screen's high pixel count, these media can be provided at higher density, which creates a sharper view.

Are pixels always the same size?

It is often formatted as width x height or pixels per inch. Because pixels aren't always the same size, it is possible to have two devices with the same screen size and different resolutions. The higher the resolution, the higher the image quality and more detail included in the image.

How do I get the screen size in CSS?

Use window. innerWidth and window. innerHeight to get the current screen size of a page.


1 Answers

The problem you're running into here isn't in the CSS layer but is lower level - in the browser rendering engine, and to some extent the physical display hardware.

why CSS rounds a single float number to three different numbers?

This isn't right - the CSS is behaving correctly and is producing exactly the same height value for all three divs in each set. You can verify this by inspecting the elements in dev tools and looking at the calculated height property for each div.

However, that value isn't in whole pixels, it's a fraction of a pixel. And you've hit a classic problem in computer graphics - how do you display fractions of a pixel on a display with discrete pixels?

Short answer -- you can't, perfectly. That's why these three fractional pixel height divs ultimately render at what looks like different heights as you resize them and even move them around on screen. It's the rendering engine doing its best to make them look identical, with varying results.

The reason your manual 'rounding' solution in JS works is because, well, it rounds those heights to whole pixel values! When the pixel values are round, it's much easier for everything to line up perfectly (in the naive case, assuming there isn't fractional pixel padding, margin etc between the divs) and you are less likely to see differences.

Try it! If you remove the Math.round or even simply add 0.75 or something to the rounded result, you'll see the exact same issue with your JS solution, even though all the calculated values are obviously identical.

Again, the problem isn't that the values are different, it's that they're rendered differently by your browser / hardware. The only universally 'safe' workaround is to ensure that the values are in fact round pixel values.

Leading onto this question:

is there any pure CSS solution to safely use viewport sizes

In terms of the above, getting rounded pixel value results, unfortunately no. There is no way with CSS calc() or otherwise to round results dynamically.

As a general suggestion, if you need precise rendering down to the pixel for thin lines etc, responsive CSS probably isn't the tool you want to use. You probably want to stick with static pixel values, but if you really need things to be dynamic and grow and shrink as you change the viewport size, then yes probably you'll want to involve JS for greater control of the rendering.

like image 76
davnicwil Avatar answered Sep 27 '22 20:09

davnicwil