Makan
Makan

Reputation: 709

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?

Upvotes: 7

Views: 1650

Answers (4)

arslan
arslan

Reputation: 1144

its weird but it always works for me use vw also for height and width. and also try min-height

Upvotes: 0

davnicwil
davnicwil

Reputation: 30967

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.

Upvotes: 7

Brain Foo Long
Brain Foo Long

Reputation: 2087

For me it looks like the following. The result differs by 1px. That reason is because the calculated height of a div is not an integer, it's a float with subpixel values. And a screen cannot display subpixels. So it depends on the position on screen if the browser decide to round to lower or higher value.

enter image description here

Upvotes: 2

Partha Paul
Partha Paul

Reputation: 309

I solved this by using 'px' in stead of 'vh'

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

Since, 'vh' is Relative to the viewport’s height, so it may fluctuate when resizing the viewport.

• 'px' is an absolute unit; but 'vh' is a relative unit.

Upvotes: 1

Related Questions