为什么 `1vh` CSS 大小解析为屏幕上不同的像素大小

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

我有三个 div,它们的高度都等于 1vh`。

我的问题是它们在屏幕上显示的像素大小不同。有时它们看起来相等但有时不相等,特别是在视口调整大小后发生。请 运行 片段查看。

.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>

我截取了一些屏幕截图来演示调整大小期间实际发生的情况,如您所见 div 看起来高度不同,尽管它们都有 1vh 高度。

如果我在 javascript 中手动计算 1vh 并将其作为四舍五入的 'px' 注入到 CSS 那么它就可以完美地工作。(在片段中我使用了 .3, .7、.9、1.1 vh 用于演示)

  
  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>

我的第一个想法是将浮点数四舍五入,但如果是这种情况,为什么 CSS 将单个浮点数四舍五入为三个不同的数字?

我的问题是为什么会发生这种情况,是否有任何纯粹的 CSS 解决方案来安全地使用视口大小并确保它们始终按预期显示?

我用 'px' 代替 'vh'

解决了这个问题
.container div{
  background: #000;
  width:100%;
  height:6px;
}

由于'vh'是相对于视口的高度,因此在调整视口大小时它可能会波动。

• 'px'为绝对单位;但是 'vh' 是一个相对单位。

对我来说,它看起来像下面这样。结果相差 1px。这是因为 div 的计算高度不是整数,它是具有子像素值的浮点数。屏幕不能显示子像素。因此,如果浏览器决定舍入为更低或更高的值,则取决于屏幕上的位置。

您在此处 运行 遇到的问题不在 CSS 层中,而是在较低级别 - 在浏览器渲染引擎中,在某种程度上在物理显示硬件中。

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

这是不对的 - CSS 的正确行为,并且为每组中的所有三个 div 生成完全相同的高度值.您可以通过检查开发工具中的元素并查看每个 div.

的计算高度 属性 来验证这一点

但是,该值不是完整像素,而是像素的一小部分。您遇到了计算机图形学中的一个经典问题 - 如何在具有离散像素的显示器上显示像素的分数?

简短回答 -- 你不能,非常好。这就是为什么当您调整它们的大小甚至在屏幕上四处移动时,这三个小数像素高度 div 最终会以 看起来 不同的高度呈现。这是渲染引擎尽最大努力使它们看起来相同,但结果不同。

您的 JS 手动 'rounding' 解决方案之所以有效,是因为它会将这些高度四舍五入为整个像素值!当像素值是圆形时,一切都更容易完美排列(在天真的情况下,假设 divs 之间没有小数像素填充,边距等)并且你不太可能看到差异.

试试吧!如果您删除 Math.round 或什至只是将 0.75 或其他内容添加到舍入结果中,您将看到与您的 JS 解决方案完全相同的问题,即使所有计算值显然相同。

同样,问题不在于值不同,而在于您的浏览器/硬件对它们的呈现方式不同。唯一通用的 'safe' 解决方法是确保这些值实际上是圆形像素值。

进入这个问题:

is there any pure CSS solution to safely use viewport sizes

就上述而言,不幸的是没有得到四舍五入的像素值结果。 CSS calc() 无法动态舍入结果。

作为一般性建议,如果您需要精确渲染细线等像素,响应式 CSS 可能不是您想要使用的工具。您可能想坚持使用静态像素值,但如果您真的需要动态的东西,并且随着视口大小的变化而增长和缩小,那么是的,您可能希望让 JS 参与进来以更好地控制渲染。

它很奇怪,但对我来说总是有效使用 vw 也用于高度和宽度。并尝试最小高度