CSS3 100vh在移动浏览器中不是恒定的


285

我有一个非常奇怪的问题……在每个浏览器和移动版本中,我都遇到以下问题:

  • 加载页面时,所有浏览器都有一个顶部菜单(例如,显示地址栏),当您开始滚动页面时,该菜单会向上滑动。
  • 有时仅在视口的可见部分计算100vh ,因此当浏览器栏向上滑动时100vh会增加(以像素为单位)
  • 由于尺寸已更改,所有布局都会重新绘制和重新调整
  • 对用户体验的不良影响

如何避免这个问题?当我第一次听说viewport-height时,我很兴奋,我以为可以将其用于固定高度块而不是使用javascript,但是现在我认为唯一的方法实际上是带有一些resize事件的javascript ...

您可以在以下示例中看到问题:

谁能帮我/建议CSS解决方案?


简单的测试代码:


1
如果我很好地理解了这个问题,那么您所面临的问题是在移动浏览器中,高度是否超过可见的视口高度。
加拉夫·阿格加瓦尔

有趣的是,以前从未注意到。其主要是明显跳动的背景图片。您如何添加一个transition: 0.5s左右,以使更改不那么突然呢?
C14L

@GauravAggarwal nope,恰恰相反:实际的视口高度大于浏览器在其地址栏可见时提供的视口高度...
Nereo Costacurta,2016年

1
由于我的问题越来越流行,我想给我5美分:保持真正的窗口高度并仅向上滑动菜单栏会更聪明吗?似乎并不困难。实际上应该更容易...手指向上->菜单栏向上滑动直到不可见,手指向下->菜单栏向下滑动直到完全可见...所有这些都与身体完全相同,没有任何重新调整和跳跃的效果...
Nereo Costacurta

4
Google在此方面提供了一些很好的信息:developers.google.com/web/updates/2016/12/url-bar-resizing如果您将身高更改为100%

Answers:


203

不幸的是,这是故意的。

这是一个众所周知的问题(至少在safari mobile中),这是有意的,因为它可以防止其他问题。Benjamin Poulain回复了一个webkit错误

这完全是故意的。为了达到这一效果,我们付出了很多工作。:)

基本问题是:滚动时可见区域会动态变化。如果我们相应地更新CSS视口高度,则需要在滚动过程中更新布局。不仅看起来像狗屎,而且在大多数页面上几乎不可能以60 FPS的速度进行操作(60 FPS是iOS上的基准帧速率)。

很难向您展示“看起来很烂”的部分,但是可以想象一下,当您滚动时,内容在移动,屏幕上您想要的内容在不断变化。

无法动态更新高度,我们有几种选择:在iOS上放下视口单位,像在iOS 8之前那样匹配文档大小,使用小视图尺寸,使用大视图尺寸。

根据我们的数据,使用较大的视图尺寸是最好的折衷方案。大多数使用视口单位的网站在大多数时候看起来都很不错。

Nicolas Hoizey已对此进行了相当多的研究:https://nicolas-hoizey.com/2015/02/viewport-height-is-taller-than-the-visible-part-of-the-document-in-some-mobile -browsers.html

没有修复计划

此时,除了不能在移动设备上使用视口高度以外,您无能为力。Chrome在2016年也更改为:


7
是的,正如我所想。唯一可行的解​​决方案是脚本解决方案。据我所知,$(window).height()不受此bug的影响,所以我将采用这种方式。谢谢!
Nereo Costacurta,

3
脚本是我的唯一方法,并且效果很好。
captDaylight

452
有史以来最令人沮丧的答案。
Winnemucca

15
从Chrome版本56开始,vh始终会像隐藏网址栏一样进行计算,因此vh不会在滚动时增加,除了position:fixed。这类似于Safari上的实现。了解更多:developer.google.com/web/updates/2016/12/url-bar-resizing
Kevin Farrugia,

4
初始加载后,最新的Chrome不会更改vh或更改<html> 100%高度。这实际上很棒-因为URL栏隐藏时布局颠簸/重排看起来很可怕。Firefox并且Edge Mobile仍会动态更新vh<html>高度,从而在滚动时导致所有组件的大量撕裂和调整大小,如果将
SVG

142

您可以尝试min-height: -webkit-fill-available;使用CSS而不是100vh。应该解决


20
我会min-height: 100vh;作为-webkit-fill-available不支持的备用。
Tammy Tee

哇谢谢你!!有了这个,我不再需要“ react-div-100vh”了!
安德烈斯·伊利桑多

1
最有帮助的答案!
Gauthier

@TammyTee是正确的,最好保留min-height: 100vh;在该位置,否则它将在Firefox之类的浏览器上损坏(至少在台式机上,无论使用哪种浏览器,iOS都使用webkit)。
JoniVR

2
这是一个非常不错的解决方案,但在iOS 13中似乎有所改变—我在第一部分的底部看到了额外的空间,但它完全可以在Safari中用于iOS 12.x来工作。codepen.io/RwwL/pen / PowjvPq(您必须先进行分叉,然后在iOS上的CodePen调试模式下查看它才能真正理解我的意思)。
RwwL

27

在我的应用程序中,我这样做(打字稿和嵌套的postcss,因此请相应地更改代码):

const appHeight = () => {
    const doc = document.documentElement
    doc.style.setProperty('--app-height', `${window.innerHeight}px`)
}
window.addEventListener('resize', appHeight)
appHeight()

在你的CSS:

:root {
   --app-height: 100%;
}

html,
body {
    padding: 0;
    margin: 0;
    overflow: hidden;
    width: 100vw;
    height: 100vh;

    @media not all and (hover:hover) {
        height: var(--app-height);
    }
}

它至少可以在chrome手机和ipad上使用。什么都行不通,是当您将应用程序添加到iOS的主屏幕上并更改方向几次后-缩放级别与innerHeight值混淆了,如果找到解决方案,我可能会发布更新。

演示版


2
这实际上是解决一个非常烦人的问题的绝佳方法。
Michael Giovanni Pumo

1
我想添加一条警告,即“ @media not all and(hover:hover){”不是检测移动浏览器的防弹方法。随意使用其他东西。
安德烈亚斯牧群

我非常喜欢这种方法。我要发表的一则评论是关于事件监听器的使用。这将导致页面重新绘制,并且仅在某些情况下(对于用户来说,正确查看正确的视口(即主页的初始视图)绝对至关重要)时,我才在某些情况下使用它
Zach Gollwitzer

22

对于我建立的许多网站,客户端都会要求提供100vh的横幅广告,正如您所发现的那样,当您开始滚动时,这会导致移动设备上的“跳动”体验变差。这是我解决问题的方法,可在所有设备上获得流畅一致的体验:

我首先将横幅元素CSS设置为 height:100vh

然后,我使用jQuery获取横幅元素的高度(以像素为单位),并使用此高度应用内联样式。

var viewportHeight = $('.banner').outerHeight();
$('.banner').css({ height: viewportHeight });

这样做解决了移动设备上的问题,因为页面加载时,banner元素使用CSS设置为100vh,然后jQuery通过将内嵌CSS放在我的banner元素上来覆盖此元素,从而在用户开始滚动时阻止其调整大小。

但是,在桌面上,如果用户调整浏览器窗口的大小,则横幅元素不会调整大小,因为由于上述jQuery,横幅元素现在具有固定的高度(以像素为单位)。为了解决这个问题,我使用“ 移动检测 ”在文档正文中添加了“移动”类。然后将上面的jQuery包装在if语句中:

if ($('body').hasClass('mobile')) {
  var viewportHeight = $('.banner').outerHeight();
  $('.banner').css({ height: viewportHeight });
}

结果,如果用户在移动设备上,则在页面的主体上出现“移动”类,并执行上述jQuery。因此,我的banner元素只会将内联CSS应用于移动设备,而在台式机上,原始的100vh CSS规则仍然存在。


4
我将对其进行调整以$('.banner').css({ height: window.innerHeight });代替使用,这是视口的“实际”高度。 innerHeight的工作方式示例
马丁

8
这是document.querySelector('.banner').style.height = window.innerHeight;人编写的JavaScript实际。
Fabian von Ellerts,

18

看看这个答案:https : //css-tricks.com/the-trick-to-viewport-units-on-mobile/

// 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`);

// 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`);
});
body {
  background-color: #333;
}

.module {
  height: 100vh; /* Use vh as a fallback for browsers that do not support Custom Properties */
  height: calc(var(--vh, 1vh) * 100);
  margin: 0 auto;
  max-width: 30%;
}

.module__item {
  align-items: center;
  display: flex;
  height: 20%;
  justify-content: center;
}

.module__item:nth-child(odd) {
  background-color: #fff;
  color: #F73859;
}

.module__item:nth-child(even) {
  background-color: #F73859;
  color: #F1D08A;
}
<div class="module">
  <div class="module__item">20%</div>
  <div class="module__item">40%</div>
  <div class="module__item">60%</div>
  <div class="module__item">80%</div>
  <div class="module__item">100%</div>
</div>


2
聪明的解决方案,我正面临着移动设备上的视口高度问题,这也应被视为解决方案(特别是对于移动设备)
Julio Vedovatto

非常好的解决方案。就我而言,我没有使用ponyfill,而是逻辑上,对于所有桌面站点,我都使用100vh,而对于移动设备,我使用var(移动世界中没有IE11)。
FrEaKmAn

唯一适用于嵌套元素的解决方案。在必须替换数百个实例的任何其他人中,您可以(至少在大多数情况下)匹配正则表达式(-)?([\ d。] +)vh并将其替换为calc(var(-vh)* $ 1 $ 2)
ecc521


5

对我来说,这样的把戏很成功:

height: calc(100vh - calc(100vh - 100%))

它与高度不同吗:100%?
FF_Dev

是的,100vh是视口高度(您的设备)的100%,当纯100%仅填充所有可用的父区域(父块可以具有例如50px的高度,它将仅填充到该父高度)。简而言之。
Patryk Janik

5

几天来我一直在寻找解决方案,这是将VueJS与Vuetify一起使用的每个人的地方(我的解决方案使用v-app-bar,v-navigation-drawer和v-footer):我创建了App.scss(在App中使用)。 vue)具有以下内容:

.v-application {
    height: 100vh;
    height: -webkit-fill-available;
}

.v-application--wrap {
    min-height: 100vh !important;
    min-height: -webkit-fill-available !important;
}


1
这很好用,并且可以在Vue / Vuetify世界之外推广。
狮子座

4

@nils清楚地解释了这一点。

那接下来是什么?

我只是回过头来在CSS中使用相对的“经典” %(百分比)

实现某些事情通常比使用它要付出更多的努力vh,但是至少,您有一个相当稳定的解决方案,该解决方案可以在不同的设备和浏览器上运行,而不会出现奇怪的UI故障。


1
高度:横幅广告的100%仍然会在移动浏览器上跳跃。我已经在Android上对其进行过测试,到目前为止,Chrome和Opera Mini的可见VP高度为100%,并且在滚动时不会改变。Edge和Firefox更改100%的高度,然后重新绘制视图。Firefox尤其糟糕,它会撕裂,重新渲染文本,并且显然会重新布局
Drenai

1
@Drenai您确定正确实现了吗?您可以在jsfiddle.net上的一个小例子中证明这一点吗?
klimat

是的,我确定:-)
Drenai

3

我刚刚发现我设计的Web应用程序在iPhone和iPad上存在此问题,并且发现了一篇文章,建议使用针对特定Apple设备的媒体查询来解决该问题。

我不知道我是否可以在此处共享该文章中的代码,但是地址是这样的:http : //webdesignerwall.com/tutorials/css-fix-for-ios-vh-unit-bug

引用文章:“使用针对旧版本iPhone和iPad分辨率的媒体查询,使元素高度与设备高度匹配。”

他们仅添加了6个媒体查询来适应全高元素,并且它完全由CSS实现,因此可以正常工作。

待编辑中:我目前无法测试,但我会回来报告结果。


23
仍在等待那些结果😉–
gman

2
对不起,我没有按承诺回来。但是在摆弄HTML和CSS的时间长于我想要的时间之后,我改变了布局的设计,发现了一种可以满足我的需求的方法,但不是通用解决方案。
Jahaziel

2

由于是新手,所以我无法对其他答案发表评论。

如果有人正在寻找使之可行的答案(并且可以使用javascript-目前似乎需要使用javascript),这种方法对我来说效果很好,并且它也说明了移动方向的变化。我将Jquery用于示例代码,但应该可以与vanillaJS一起使用。

-首先,我使用脚本来检测设备是触摸还是悬停。裸露的例子:

if ("ontouchstart" in document.documentElement) {
    document.body.classList.add('touch-device');

} else {
    document.body.classList.add('hover-device');
}

这根据设备类型(悬停或触摸)将类添加到body元素,以后可用于高度脚本。

-接下来使用此代码设置设备在负载和方向变化时的高度:

if (jQuery('body').hasClass("touch-device")) {
//Loading height on touch-device
    function calcFullHeight() {
        jQuery('.hero-section').css("height", $(window).height());
    }

    (function($) {
        calcFullHeight();

        jQuery(window).on('orientationchange', function() {
            // 500ms timeout for getting the correct height after orientation change
            setTimeout(function() {
                calcFullHeight();
            }, 500);

        });
    })(jQuery);

} else {
    jQuery('.hero-section').css("height", "100vh");


}

-设置超时,以便设备可以在方向更改时正确计算新高度。如果没有超时,以我的经验,高度将不正确。500ms可能过大,但对我有用。

如果浏览器覆盖CSS 100vh,则悬停设备上的-100vh是后备。


2
Touchstart不支持所有浏览器,以及一些非移动设备具有它developer.mozilla.org/en-US/docs/Web/Events/touchstart
Drenai

大多数移动浏览器都支持@Drenai Touchstart。在台式机上支持较少,IE,Opera和Safari不支持。但是,最初的问题是针对移动设备而非台式机的。这使之成为有效答案。
保罗

感谢您的评论!我主要使用这种策略来克服某些移动浏览器上屏幕的调整和跳动。如果浏览器不支持touchstart,则回退将是CSS 100vh-如果不支持100vh,则是另一个主题。如果桌面设备碰巧具有触摸支持,则将使用javascript计算高度。然后,我们将失去对100vh提供的调整大小的重新计算,但这通常不是致命的问题,因为台式机上的方向没有变化。
tjgoodman

同样,高度计算也可以附加到调整大小事件中,但随后我们可能会遇到相同的精确问题,即移动设备上的重新调整和跳跃性。因此,我发现这种策略是可行的。
tjgoodman

1
@Paul如果Chrome和Firefox桌面浏览器具有touchstart,那么确定检测移动浏览器使用的功能肯定错误吗?我刚刚在Windows上的Chrome和Firefox中对其进行了测试,并且都通过了此测试
Drenai

2

以下代码(使用jQuery)解决了该问题。

var vhHeight = $("body").height();
var chromeNavbarHeight = vhHeight - window.innerHeight;
$('body').css({ height: window.innerHeight, marginTop: chromeNavbarHeight });

并且其他元素%用作替换的单元vh


2

这是我用于我的React应用程序的一种解决方法。

iPhone 11 Pro和iPhone Pro Max-120像素

iPhone 8-80像素

max-height: calc(100vh - 120px);

这是一个折衷,但相对简单的修复


1

以下为我工作:

html { height: 100vh; }

body {
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  width: 100vw;
}

/* this is the container you want to take the visible viewport  */
/* make sure this is top-level in body */
#your-app-container {
  height: 100%;
}

body将可视视口的高度,并#your-app-containerheight: 100%将使该容器采取可见视口的高度。



0

因为不会被修复,所以您可以执行以下操作:

# html
<body>
  <div class="content">
    <!-- Your stuff here -->
  </div>
</body>

# css
.content {
  height: 80vh;
}

对我来说,这是比使用JavaScript最快,更纯净的解决方案,而JavaScript无法在许多设备和浏览器上运行。

只要使用vh适合您需求的适当值即可。


0

在移动设备上使用vh不能与100vh一起使用,因为它们的设计选择是使用设备的整个高度,不包括任何地址栏等。

如果要查找包含与真实视图高度成比例的div高度的布局,请使用以下纯CSS解决方案:

:root {
  --devHeight: 86vh; //*This value changes
}

.div{
    height: calc(var(--devHeight)*0.10); //change multiplier to suit required height
}

您有两个选项来设置视口高度,将--devHeight手动设置为有效的高度(但是您需要为要编码的每种类型的设备输入该值)

要么

使用javascript获取窗口高度,然后在加载和刷新视口时更新--devheight(但是这确实需要使用javascript,并且不是纯CSS解决方案)

一旦获得正确的视图高度,就可以通过简单地更改分配高度的每个div中的乘数,以总视口高度的确切百分比创建多个div。

0.10 =视图高度的10%0.57 =视图高度的57%

希望这可以帮助某人;)


1
那是一个很好的解决方案。您可以在Css-tricks上的这篇文章中阅读有关此内容的更多信息。
Amaury Hanser,

0

您可以通过添加以下脚本和样式来实现

  function appHeight() {
    const doc = document.documentElement
    doc.style.setProperty('--vh', (window.innerHeight*.01) + 'px');
  }
  window.addEventListener('resize', appHeight);
  appHeight();

样式

.module {
 height: 100vh; /* Fallback for browsers that do not support Custom Properties */
  height: calc(var(--vh, 1vh) * 100);
}


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.