在移动端中使用 100vh 导致页面出现滚动条的解决方法

前言

现在有如下的 HTML 结构:

1
2
3
4
5
<body>
<div class="header">Header</div>
<div class="content">Content</div>
<div class="footer">Footer</div>
</body>

如果我们想给页面一个最小高度,保证 Header 和 Footer 分别位于页面的顶部和底部,我们通常会这样写样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
* {
padding: 0;
margin: 0;
}

body {
min-height: 100vh;
/* flex 布局 */
display: flex;
flex-direction: column;
}

.content {
background-color: skyblue;
flex: 1;
}

在浏览器中我们预览的效果如下:

image.png|350

但是如果在移动端使用某些浏览器,比如 iOS 的 Safari,就会出现纵向滚动条:

20240527210914.png|753

这是因为某些移动端浏览器在计算 vh 时,会将工具栏高度也计算进去,因此会出现滚动条,我们可以通过如下三种方案去尝试修复这个行为。

方案一:使用 -webkit-fill-available

-webkit-fill-available 是 webkit 浏览器独有的一个属性值,表示填充剩余可用空间,因此我们可以将 bodymin-height 设置为该值,就可让 body 填充整个视口了:

1
2
3
4
5
6
7
8
9
10
11
12
13
body {
/* 不支持 -webkit-fill-available 的回落到 100vh */
min-height: 100vh;
min-height: -webkit-fill-available;
/* flex 布局 */
display: flex;
flex-direction: column;
}

.content {
background-color: skyblue;
flex: 1;
}

设置后我们发现 safari 的高度正常了,但是 PC 和 Android 的 Chrome 浏览器高度却不对了:

image.png|350

这是由于 -webkit-fill-available 两者的表现形式不统一造成的:

  • 在 Safari 中:如果设定了 height: -webkit-fill-available 元素父级元素设定了绝对的宽高,那么其元素高度就是父级元素的高度。如果父级元素没有宽高,那其高度就是视口的宽度,可用于替代 100vh 让移动端不出现滚动条;
  • 在 Chrome 中:只有 html 元素设置了 height: -webkit-fill-available 才会填充整个视口宽度,如果想让子元素的高度也为视口高,那就需要层层设置 height: -webkit-fill-available

因此如果想要兼容 Chrome 的表现,就需要将 CSS 修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
html {
height: -webkit-fill-available;
}

body {
/* 不支持 -webkit-fill-available 的回落到 100vh */
min-height: 100vh;
min-height: -webkit-fill-available;
/* flex 布局 */
display: flex;
flex-direction: column;
}

.content {
background-color: skyblue;
flex: 1;
}

注意:html 必须设置 height 而不能设置 min-height,这是因为 body 要继承 html 的高度,如果不指明,子元素的 min-height: -webkit-fill-available 就相当于 min-height: auto

该方法较为简单,CSS 兼容性也尚可,但是如果元素嵌套过深,比如:

1
2
3
4
5
6
7
<body>
<div class="container">
<div class="header">Header</div>
<div class="content">Content</div>
<div class="footer">Footer</div>
</div>
</body>

我们想为 .container 设置 min-height-webkit-fill-available,那就必须将 bodyhtmlheight 设置为 -webkit-fill-available,这样 .cotainer 的最小高度才能在 Chrome 中生效。

参考:《CSS fix for 100vh in mobile WebKit》

方案二:使用 window.innerHeight

window.innerHeight 可以用于获取视口高度,在移动端浏览器中,该值不会包含工具栏的高度,因此我们可以通过该值来修正 vh

我们可以使用 CSS Var 来创建一个全局变量 --vh,该变量的值为 window.innerHeight * 0.01,也就是 1vh 的高度,然后将使用 100vh 的地方替换为 calc(var(--vh, 1vh) * 100) 即可。

代码逻辑如下:

1
2
3
4
5
6
7
8
function setVhCssVar() {
const vh = window.innerHeight * 0.01;
// 创建全局变量 --vh
document.documentElement.style.setProperty('--vh', `${vh}px`);
}

setVhCssVar();
window.addEventListener('resize', setVhCssVar);

使用:

1
2
3
body {
min-height: calc(var(--vh, 1vh) * 100);
}

这个方案虽然需要使用 Javascript,但是效果是比较好的,可以完美的替换 100vh,且不存在浏览器的差异性。

方案三:使用 dvh

dvh 表示动态视口,是一个比较新的 CSS 单位。其可以动态的表示移动端浏览器的视口高度,比如当浏览器存在工具栏、地址栏时,其表示中间的小视口的高度;而当用户向下滑动,或者手动隐藏掉工具栏时,其表示的是隐藏掉栏框后的大视口高度:

image.png|350

因此我们只需要将 vh 替换为 dvh 即可完美解决滚动条问题:

1
2
3
body {
min-height: 100dvh;
}

但是该特性由于较新,考虑兼容性问题的话需要慎重使用:

image.png|350

参考:《vh 存在问题?试试动态视口单位之 dvh、svh、lvh》