CSS3 100vh在移动浏览器中不稳定

我有一个很奇怪的问题…在每个浏览器和手机版本中,我都遇到了这种行为:

  • 当你加载页面时,所有的浏览器都有一个顶部菜单(例如显示地址栏),当你开始滚动页面时,它会向上滑动。
  • 100vh 有时仅在视口的可见部分上计算,因此当浏览器栏向上滑动时,100vh增加(以像素计算)
  • 所有布局重新油漆和重新调整,因为尺寸已经改变
  • 对用户体验有不好的跳跃效果

如何避免这个问题?当我第一次听说viewport-height时,我很兴奋,我认为我可以使用它来固定高度块,而不是使用javascript,但现在我认为唯一的方法实际上是javascript与一些调整大小事件…

你可以在示例网站看到问题

有人能帮助我/建议一个CSS解决方案吗?


简单测试代码:

/* maybe i can track the issue whe it occours... */
$(function(){
var resized = -1;
$(window).resize(function(){
$('#currenth').val( $('.vhbox').eq(1).height() );
if (++resized) $('#currenth').css('background:#00c');
})
.resize();
})
*{ margin:0; padding:0; }


/*
this is the box which should keep constant the height...
min-height to allow content to be taller than viewport if too much text
*/
.vhbox{
min-height:100vh;
position:relative;
}


.vhbox .t{
display:table;
position:relative;
width:100%;
height:100vh;
}


.vhbox .c{
height:100%;
display:table-cell;
vertical-align:middle;
text-align:center;
}
<div class="vhbox" style="background-color:#c00">
<div class="t"><div class="c">
this div height should be 100% of viewport and keep this height when scrolling page
<br>
<!-- this input highlight if resize event is fired -->
<input type="text" id="currenth">
</div></div>
</div>


<div class="vhbox" style="background-color:#0c0">
<div class="t"><div class="c">
this div height should be 100% of viewport and keep this height when scrolling page
</div></div>
</div>


<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

373997 次浏览

不幸的是,这是故意的……

这是一个众所周知的问题(至少在safari mobile中),这是故意的,因为它可以防止其他问题。# EYZ0:

这完全是故意的。为了达到这个效果,我们花了不少功夫。:)

基本问题是:可视区域随着滚动而动态变化。如果我们相应地更新CSS视口高度,我们需要在滚动期间更新布局。这不仅看起来很糟糕,而且在大多数页面中,以60帧/秒的速度实现这一点几乎是不可能的(在iOS上,60帧/秒是基准帧速率)。

很难向你展示“看起来像屎”的部分,但想象一下,当你滚动时,内容在移动,你想要在屏幕上显示的内容在不断移动。

动态更新高度不起作用,我们有几个选择:在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年也变成了这样:

我刚刚发现我设计的一个web应用程序在iphone和ipad上有这个问题,并发现一篇文章建议使用针对特定苹果设备的媒体查询来解决这个问题。

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

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

他们只添加了6个媒体查询来适应全高度元素,它应该可以工作,因为它是完全由CSS实现的。

编辑等待:我现在无法测试它,但我会回来报告我的结果。

对于我所创建的许多网站,客户端都会要求100vh的横幅,正如你所发现的那样,当你开始滚动时,它会导致糟糕的“跳跃”体验。这就是我如何在所有设备上获得流畅一致的体验:

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

然后我使用jQuery来获得我的横幅元素的像素高度,并使用这个高度应用内联样式。

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

这样做解决了移动设备上的问题,当页面加载时,使用CSS将横幅元素设置为100vh,然后jQuery通过在我的横幅元素上放置内联CSS来阻止它在用户开始滚动时调整大小。

然而,在桌面上,如果用户调整他们的浏览器窗口大小,我的横幅元素不会调整大小,因为它现在有一个固定的高度像素设置由于上述jQuery。为了解决这个问题,我使用移动侦测在我的文档正文中添加了一个“移动”类。然后我将上面的jQuery封装在if语句中:

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

因此,如果用户在移动设备上,“mobile”类就会出现在我的页面主体上,并且会执行上面的jQuery。所以我的横幅元素只会得到内联CSS应用在移动设备上,同时在桌面上,原来的100vh CSS规则仍然存在。

因为我是新人,我不能对其他答案发表评论。

如果有人正在寻找一个答案,使这个工作(可以使用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");




}

-Timeout设置,以便设备在方向改变时正确计算新的高度。如果没有超时,根据我的经验,高度是不正确的。500毫秒可能有点过分,但对我来说很有效。

如果浏览器覆盖CSS的100vh,则-100vh在hover-devices上是一个备用选项。

@nils解释得很清楚。

接下来呢?

我只是在CSS中使用相对“经典”# EYZ0(百分比)

实现一些东西通常比使用vh要花费更多的精力,但至少,您有一个相当稳定的解决方案,可以跨不同的设备和浏览器运行,没有奇怪的UI故障。

我想出了一个React组件-如果你使用React, 看看吧,如果你不使用React, 浏览源代码,这样你就可以适应你的环境。

它将全屏div的高度设置为window.innerHeight,然后在窗口大小调整时更新它。

在我的应用程序,我这样做(typescript和嵌套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值,我可能会发布一个更新,如果我找到一个解决方案。

< a href = " https://mendrik.github。io / preact-easy-state noreferrer“rel = >演示< / >

下面的代码(使用jQuery)解决了这个问题。

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

其他元素使用%作为单位替换vh

因为不会被修复,你可以这样做:

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


# css
.content {
height: 80vh;
}

对我来说,这是比JavaScript更快、更纯粹的解决方案,因为JavaScript无法在许多设备和浏览器上运行。

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

下面的方法对我很有效:

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%将使容器使用可见视口高度。

看看这个答案: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>

您可以尝试将position: fixed; top: 0; bottom: 0;属性赋予容器。

你可以在css中尝试min-height: -webkit-fill-available;而不是100vh。应该得到解决

在移动设备上使用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%的视图高度

希望这可以帮助到一些人;)

下面是我在React应用程序中使用的一个方法。

iPhone 11 Pro &iPhone Pro Max - 120px

iPhone 8 - 80px

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

这是一个折衷方案,但相对简单

对我来说,这种把戏成了一种工作:

height: calc(100vh - calc(100vh - 100%))
当我在寻找一个解决方案的时候,这里是我的每个人使用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;
}

您可以通过添加以下脚本和样式来做到这一点

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);
}

尝试html, body { height: 100% }在移动设备上达到100vh的效果。

VH 100在移动设备上表现不佳,因为它没有考虑iOS栏(或其他平台上的类似功能)。

一个很好的解决方案是使用JavaScript的“window. innerheight”。

简单地将元素的高度赋值为这个值。 $ (' .element-name ') .height (window.innerHeight); < / p >

注意:在JS中创建一个函数可能很有用,这样当屏幕调整大小时,高度就可以改变。但是,我建议只在屏幕宽度改变时调用该函数,这样当用户向下滚动页面时iOS栏消失时,元素的高度就不会跳跃。

关于这个问题及其可能的解决方案,可以在这篇博客文章中找到:在100vh布局中寻址iOS地址栏

我最终在我的React应用程序中的解决方案是利用上面帖子中描述的反应100 - vh - div库。

Brave浏览器在iOS上的表现不同(有bug ?)它根据显示/隐藏地址栏动态改变视口高度。这有点烦人,因为它改变页面的布局取决于vw/vh单位。

Chrome和Safari都没问题。

不要使用推荐的方法,例如-webkit-fill-available
我花了一整天的时间来解决这个“bug”。

添加类当你的应用程序加载浏览器与quot;chin"

JavaScript

// Angular example but applicable for any JS solution
@HostBinding('class.browser-has-chin') browserHasChin: boolean = false;


public ngOnInit(): void {
this.browserHasChin = this._isMobileSafari();
}


private _isMobileSafari() {
return navigator.userAgent.match(/(iPod|iPhone|iPad)/) && navigator.userAgent.match(/AppleWebKit/) ? true : false;
}

CSS

.browser-has-chin {
@media screen and (max-device-width: 767px){
// offset with padding or something
}
}
< p > # EYZ0
-webkit-fill-available道具在跨浏览器兼容性方面存在重大问题

我能够让它在Chrome和iOS Safari中工作,以修复下巴/高度计算问题。然而,它破坏了Android Chrome和Firefox也有bug。

似乎-webkit-fill-available在某种程度上是匆忙加入webkit的,也许是苹果偶然采用的,作为下巴/身高计算的修正?

它依赖于内在的分级,使用它还不安全。

我通过将最外层的div放在position: absolute,然后将height设置为100%来解决这个问题:

CSS:

.outer {
position: absolute;
height: 100%;
}

HTML:

<div class="outer">
<!-- content -->
</div>

设置你的身体位置为固定,设置高度为100%

body { position: fixed; height: 100% }

就这样,然后移动浏览器就会理解你想要什么。

现在,主体将随着浏览器的视图高度而增长或缩小,无论是否有URL栏,或者是否有标签(如在移动safari中)。身体总是能看到完整的画面。

看起来CSS修复是不可靠的,JS一个工作得很好,但元素在用户打开页面时跳跃。

我用JS from other answers + fadeIn animation隐藏跳跃来解决这个问题。

这并不适合所有的用例,但对于其中一些用例,比如必须在底部的按钮,可能是一个很好的解决方案。

在我的例子中,我有一些元素设置为90vh,另一个元素设置为70px 将70px改为10vh解决了

不要把vh和px混合

我在下面创建了两个例子:

  1. 为了展示如何height: 100vh作为高度可以导致滚动在移动chrome浏览器:

# eyz0: # eyz1

# EYZ0: # EYZ1


  1. 解决方案使用position: fixed来解决问题和纯CSS:

# eyz0: # eyz1

# eyz0: # eyz1

找到这个答案的人正在与移动视图上向下/向上滚动的固定位置的元素的连接跳跃问题作斗争,根元素需要在底部而不是顶部指定属性,并且应该在px中给出一个字段值。这就解决了问题

< p >改变:
element{
position: fixed;
top: 90vh;
left: 90vh;
transform: translate(-95%, -95%);




}

:

    element{
position: fixed;
bottom: 90px; /* this is the change */
left: 90vh;
transform: translate(-95%, -95%);




}

body {
width: 100%;
height: 100%
}

100%将自我调整到屏幕大小,但出于某种原因 100vh在屏幕变化时调整有问题,因此使用底部0将受到影响,因此,如果您使用100vh或100%

例如,当搜索栏在视图中或不在移动中,100%与变化,其中100vh将粘在屏幕高度的第一个值

我们有新的视口单元lvhsvhdvh来拯救。这在最新的谷歌I/O 2022网络作品视频中得到了证明。

您可能希望坚持使用dvh,以便浏览器在滚动时适应移动设备的隐藏选项卡。对于宽度单元dvwlvwsvw,它的工作方式类似。

这是视频中的一个简洁的插图:https://youtu.be/Xy9ZXRRgpLk?t=982

谷歌I/O 2022 on new viewport units

我可以用吗?< / >

这是目前工作在我的Chrome金丝雀与标志&;实验功能"启用。

不幸的是,这个问题至今仍然存在。最大的误导是不可能使用浏览器的devices toolbar来表示这种情况。

我刚刚解决了这个问题(在PC、iOS和android浏览器上进行了测试):

.your_class {
height: 100vh,
max-height: 100%, // <-- add the line
...some other props,
}

我希望它能节省你的时间。

问题:

if ( refDOM.clientHeight != window.innerHeight )

解决方案:

refDOM.style.setProperty('--MYvh', window.innerHeight + 'px' ) ;

完成:

const mobileShit = function ( refDOM ) {
console.log ( `refDOM.clientHeight : ${refDOM.clientHeight}` ) ;
console.log ( `window.innerHeight : ${window.innerHeight}` ) ;
if ( refDOM.clientHeight != window.innerHeight ) { // accounting for mobile
refDOM.style.setProperty('--MYvh', window.innerHeight + 'px' ) ;
}
} ;

CSS(通过脚本修改,是的,当任何事情发生变化时,你必须处理事件):

:root {
--MYvh : 100vh ;
}

React用useEffect和useState挂钩解决方案

    function App() {
const [vh, setVh] = useState(window.innerHeight);
    

useEffect(() => {
const updateVh = () => {
setVh(window.innerHeight);
};
    

window.addEventListener('resize', updateVh);
    

return () => window.removeEventListener('resize', updateVh);
}, []);
    

return (
<div style=\{\{ height: vh }}>
{vh} px
</div>
);
}

演示:# EYZ0