视窗单位一直存在争议,部分原因是移动浏览器在实现它们时有自己的想法,从而使事情变得更加复杂。
例如:滚动条是否应计入vw
单位?网站的导航或页面控件是否应计入计算?然后是设备本身的物理属性(您好,凹口!)也不能忽视。
首先,一些背景知识
关于如何计算视窗单位,规范非常含糊。对于移动设备,我们通常关注垂直高度,因此让我们专门看一下视窗高度(vh
)
vh 单位
等于初始包含块高度的 1%。
所以,在处理设备和浏览器特定的差异方面,没有明确的指导。
vh
最初是根据浏览器的当前视窗计算的。如果您打开浏览器并开始加载网站,则1vh
等于屏幕高度的 1%,减去浏览器界面。
但是!如果您开始滚动,情况就不同了。一旦您超过浏览器界面的一部分,例如地址栏,vh
值就会更新,结果是内容发生了尴尬的跳跃。
iOS 版 Safari 是第一个更新其实现的移动浏览器之一,它选择根据屏幕的最大高度定义vh
的固定值。这样,用户在地址栏消失后不会在页面上体验到跳跃。Chrome 的移动浏览器大约一年前也效仿了。
截至撰写本文时,有一个票证来解决 Firefox Android 中的这个问题。
虽然使用固定值很好,但也意味着如果地址栏可见,则无法拥有全高元素。元素的底部将被裁剪。

CSS 自定义属性:正确大小调整的技巧
我突然想到,CSS 自定义属性和几行JavaScript
代码可能是获得一致且正确大小的完美方法。
在 JavaScript 中,您可以始终使用全局变量window.innerHeight
获取当前视窗的值。该值会考虑浏览器界面,并在其可见性发生变化时更新。诀窍是将视窗值存储在 CSS 变量中,并将该值应用到元素,而不是vh
单位。
假设我们的 CSS 自定义变量在本例中是--vh
。这意味着我们将在 CSS 中这样应用它
.my-element {
height: 100vh; /* Fallback for browsers that do not support Custom Properties */
height: calc(var(--vh, 1vh) * 100);
}
好的,我们已经设置好了。现在让我们在 JavaScript 中获取视窗的内部高度
// First we get the viewport height and we multiple it by 1% to get a value for a vh unit
let vh = window.innerHeight * 0.01;
// Then we set the value in the --vh custom property to the root of the document
document.documentElement.style.setProperty('--vh', `${vh}px`);
我们告诉 JavaScript 获取视窗的高度,然后将其细化到总高度的 1/100,这样我们就有一个值可以作为视窗高度单位值分配。然后我们礼貌地要求 JS 在:root
中创建 CSS 变量(--vh
)。
因此,我们现在可以像使用任何其他vh
单位一样使用--vh
作为我们的高度值,将其乘以 100,我们就会得到我们想要的全高。
最近出现了一种更常用的方法来解决这个问题。 Matt Smith 在这里对其进行了说明。诀窍是在主体上使用min-height: -webkit-fill-available;
作为100vh
的渐进增强,这应该在 iOS 设备上有效。
等等!还有一个细节。
虽然我们的工作可能看起来已经完成了,但是那些善于观察细节的人可能已经发现,JavaScript 会触发,但当视窗高度发生变化时,它永远不会更新元素的大小。继续调整上面的演示。
我们可以通过监听窗口resize
事件来更新--vh
的值。如果用户旋转设备屏幕(例如,从横向到纵向),或者导航在滚动时消失,这将非常有用。
// We listen to the resize event
window.addEventListener('resize', () => {
// We execute the same script as before
let vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);
});
⚠️ 更新--vh
的值将触发页面重新绘制,用户可能会因此体验到跳跃。因此,我不建议将此技巧用于每个项目或完全替代所有 vh 单位的使用,而只应在您可能需要用户拥有精确的视窗单位值时使用。
此外,您可能还想对 resize 事件实现一个防抖动方法,以避免在用户调整浏览器窗口大小时触发太多事件。您可以通过这篇文章了解更多信息:防抖动和节流通过示例解释
您现在可以调整上面的演示,并注意到 CSS 变量已相应更新。
虽然我最近在项目中使用了这种技术,并且它确实很有帮助,但在替换浏览器的默认行为时,您始终要三思而后行。(例如,这种情况在::focus
中很常见。)此外,浏览器现在往往更新非常快,因此请注意,今天的解决方案可能在明天不起作用。
同时,希望这篇文章对您有所帮助!👋
这里有一个提议,用于vhc
和vwc
单位,这可能是所有问题的救星。
您应该在监听 resize 事件时使用某种节流,尤其是在它触发重新绘制时 - 例如,如以下文档所示:https://devdocs.io/dom_events/resize
感谢您的反馈,我在防抖技术部分添加了一小段说明,但我不想在演示中添加额外的代码,以保持演示的清晰性✌️
还没有尝试过,但我为这个问题苦苦挣扎了几个月!非常感谢,我会尽快尝试这个方法来修复我的网站。
您能详细说明您的用例吗?您需要在页面顶部创建一个内联全高元素吗?
我在某个地方(我认为是 nngroup.com)读到,最好让这样的元素稍微小一些,以便用户知道他们可以向下滚动。(据称,一些用户会认为下面没有内容。)
这正是我的用例。网站的第一个屏幕应该在移动设备上全高显示,但我们发现底部被裁剪了(这是客户的徽标)。
如果您有一个模态应该为 100vh,并且您不希望用户因为浏览器界面而丢失底部部分,也可以使用此技巧。
我一个月前遇到了同样的问题,并且想出了类似的解决方案:D
我没有在根元素中使用 –vh,而是在需要 vh 单位的元素中使用,并且使用了 jQuery,因为项目是用 jQuery 编写的。但概念是一样的。
类似的修复方法,当 body 标签的 overflow 为 hidden 时,使用模态覆盖。
在 js 中
function getScrollbarWidth() {
return window.innerWidth – document.documentElement.clientWidth;
}
document.documentElement.style.setProperty(‘–scrollbar-width’,
${getScrollbarWidth()}px
);在 css 中
body.modal-opened { padding-right: calc(var(–scrollbar-width)); }
只是一个旁注 - 可能最好将变量命名为 –vh100,因为它代表的是“100vh”,而不是单个单位。
如果您只需要全高元素,是的,您可以跳过
calc
部分,并将变量设置为 window.innerHeight 的 100%。但是,如果您需要元素为 50vh 或其他高度,您可以使用变量并将其乘以以下方式:height: calc(var(--vh, 1vh) * 50);
一个非常棒的解决方案。
但是,有一个问题 - 如果任何脚本执行失败,JS 无法加载或加载时间过长,您的网站将无法使用。
在 body 标签中添加一个 .js 类,并使 calc 高度仅在 JS 加载后生效 - 100vh 既是备用方案,也是非 JS/缓慢加载版本
如果 JavaScript 没有运行,CSS 中已经有备用方案。
在这行代码中:
height: calc(var(--vh, 1vh) * 100);
有var(--vh, 1vh)
,其中1vh
是备用方案。这篇文章没有提到,但是 CSS 自定义属性如果未定义属性,可以有备用方案。
你可以在这里了解更多信息:https://mdn.org.cn/en-US/docs/Web/CSS/var
你也可以在 CSS 中的根元素上添加一个默认值。
干杯 ✌️
你好,
我使用以下代码,从未遇到过 100vh 没有真正占用整个高度的问题。
这消除了默认行为。对我来说,你遇到的问题似乎是由它引起的。
之后,我只根据需要手动添加边距和填充。
这很奇怪。你可以在手机上查看这个实时演示,并告诉我你看到了什么?

这是手机版 Chrome 的截图。如你所见,
100vh
指的是没有界面显示的屏幕。我在手机上的 Chrome 上成功运行了,但它在 Safari 上不起作用 :(
路易斯,请忽略我的第一条评论,我自己调查了问题后,发现比我预期的更多。我一直认为视窗高度就是,你知道的,视窗高度。并不是它实际上是的那样混乱。
所以,我研究了一下... 似乎唯一有点可靠的解决方案是你在你的文章中提到的,也许再加一些媒体查询... 我目前正在研究它。
与此同时,我做了一个小演示,它似乎工作正常,有点... 我会尝试使用 orientationchange 事件监听器来处理方向变化,并手动调整高度低于前两个屏幕高度的页面,因为 URL 栏在此时将始终隐藏。
http://www.patriklegard.com/app
谢谢 - 这个问题困扰了我很久,现在似乎很明显,但实际上我从未想到用这种方式使用 innerheight 来解决问题。
但我并不建议使用 resize 事件,因为元素的高度会在你滚动手机时强制改变(尤其是在 iOS 的 Safari 上很明显)。这意味着,如果元素上有一个设置为覆盖的背景图片,背景位置就会改变,也会影响该元素内部的任何绝对定位元素。
为了避免这个问题,我只在 resize 变化足够大的时候(或者在这种情况下,任何横向模式,主要是桌面用户)才让脚本更新我的
vh
变量。我在一个带粘性页脚的 aside 中遇到了这个问题。我一直希望页脚可见,因为它包含取消和提交按钮,但根据滚动,它可能显示正确,也可能不显示。你对移动设备上的 aside 中的粘性页脚有什么建议吗?
以防万一其他人遇到这个问题,显然在 Chrome 中,如果你在 Dev Tools 的设备模式下,window.innerHeight 不会返回正确的视窗高度。我试图在我的个人网站上使用这种方法,但当我在设备模式下查看它在 iOS 上的外观时,我很困惑,因为控制台日志显示的 innerHeight 值与设备视窗高度不同。Firefox 和 Safari 显示了正确的值,但 Chrome 没有。
我找到了这篇文章,它似乎澄清了原因。
https://developers.google.com/web/updates/2017/09/visual-viewport-api
如果我想让 Chrome 使用设备的视觉视窗,我需要在控制台中记录 window.visualViewport.width。但如果你真的在你的移动设备上,innerHeight 可以正常工作,只是当你用 Chrome Dev Tools 模拟移动设备时,innerHeight 不会像你预期的那样工作。
哎呀,我的意思是 window.visualViewport.height
只是挑剔一下,但你最好把那个
let
替换成const
。