起始
最近H5 项目,遇到了一个「有趣」的问题,首先我们看一个简单的demo,一个列表,从1-100
<div class="container">
<div class="page"></div>
<div class="tabbar">TabBar</div>
</div>
.page {
background-color: #f8f8f8;
height: calc(100vh - 50px);
overflow-y: scroll;
}
.tabbar {
position: absolute;
bottom: 0;
width: 100%;
height: 50px;
line-height: 50px;
background-color: #fff;
box-sizing: border-box;
}
这样看着是没有什么问题的,我们在iPhone 的 Safari 浏览器打开此页面试一下,以iphoneX 15.0
以及ios 13 15.4
的系统为例。
iphoneX 15.0
ios 13 15.4
我们可以看到,页面刚一开始进去是正常的,但是iphonex在下滑的过程中,顶部和底部导航栏会收缩起来。iphone13的状态栏会一直保持,但是底部有被遮挡。
原因
我们知道,vh 是 CSS 中的一种相对长度单位,1vh
表示 viewport 高度的 1%)。简单来讲,viewport 基本上是指当前文档的可见部分,因此 100vh 表示可见文档的最大高度。
按上面所说,100vh 不应该是 viewport 可视区域的全部高度,为什么右图的高度会超出的呢?
可以看一张简图:
这就是为什么在 Safari 会被挡住一部分的原因。
为什么
这是个bug,还是其它什么原因么?
这里有一篇文章,有一个用户,给 WebKit 提了个 bug,其中 Apple 工程师 Benjamin Poulain 的回答如下,什么意思呢?
这完全是故意的。我们花了很多功夫才达到这个效果。:)
基本问题是:滚动时可见区域会动态变化。如果我们相应地更新 CSS 视口高度,我们需要在滚动期间更新布局。这不仅看起来很糟糕,而且在大多数页面中以 60 FPS 的速度执行此操作实际上是不可能的(60 FPS 是 iOS 上的基准帧率)。
很难向您展示“看起来很糟糕”的部分,但想象一下,当您滚动时,内容会移动,屏幕上您想要的内容也在不断变化。
动态更新高度不起作用,我们有几个选择:在 iOS 上删除视口单位,像 iOS 8 之前一样匹配文档大小,使用小视图大小,使用大视图大小。
从我们拥有的数据来看,使用较大的视图尺寸是最好的折衷方案。大多数使用视口单位的网站在大多数时候看起来都很棒。
然而,并不是只有 Safari 是这样做的,比如 iOS 端 Chrome 浏览器表现与 iOS 端 Safari 一致。
解决问题
方案一:使用 -webkit-fill-available
.page {
background-color: #f8f8f8;
min-height: 100vh;
min-height: -webkit-fill-available;
overflow-y: scroll;
padding-bottom: 50px; // 注意,动态设置距离底部的高度
}
.tabbar {
position: fixed;
bottom: 0;
width: 100%;
height: 50px;
line-height: 50px;
background-color: #fff;
box-sizing: border-box;
}
然而,以上方案并不适用于这些情况,比如:height: 90vh
、height: calc(100vh - 50px)
方案二: 通过 CSS 变量计算 1vh 所表示的实际高度 思路:
设置一个 CSS 变量(比如 --vh),然后通过 JavaScript 脚本动态设置 --vh 的值,然后使用时需兼容处理即可 比如,
height: 100vh; height: calc(var(--vh) * 100)
:root {
--vh: 1vh;
}
.page {
background-color: #f8f8f8;
height: calc(var(--vh) * 100 - 50px);
overflow-y: scroll;
}
.tabbar {
position: absolute;
bottom: 0;
width: 100%;
height: 50px;
line-height: 50px;
background-color: #fff;
box-sizing: border-box;
}
function setViewHeight() {
// 计算当前窗口高度的 1% 的值,用作 CSS 自定义属性的值
var windowVH = window.innerHeight / 100
// 将计算得到的窗口高度的 1% 的值设置为名为 --vh 的 CSS 自定义属性的值,单位为像素
document.documentElement.style.setProperty('--vh', windowVH + 'px')
}
// 在页面加载完成后,调用 setViewHeight 函数,即初始化时计算并设置视图的高度
document.addEventListener('DOMContentLoaded', setViewHeight)
// 当屏幕窗口大小改变时,重新计算并设置视图的高度
window.addEventListener('resize', setViewHeight)
方案三:postcss-100vh-fix github.com/postcss/pos…
了解规则集之前需要先了解一下两个属性@supports,-webkit-touch-callout,-webkit-fill-available。
@supports
@supports CSS at-rule 您可以指定依赖于浏览器中的一个或多个特定的 CSS 功能的支持声明。这被称为特性查询。该规则可以放在代码的顶层,也可以嵌套在任何其他条件组规则中。
通过使用 @supports 规则,你可以根据不同浏览器的支持情况,为不同的 CSS 功能设置不同的样式。
@supports (display: grid) {
div {
display: grid;
}
}
@supports not (display: grid) {
div {
float: right;
}
}
-webkit-touch-callout
-webkit-touch-callout 这个CSS 属性禁用了默认的 callout 展示,callout 是指当触摸并按住一个元素的时候出现的提示。
当在 iOS 上一直按住一个目标元素时,Safari 会展示一个关于这个链接的 callout 信息。webkit-touch-callout属性允许禁用掉这一行为。
-webkit-fill-available
css的自适应关键字,其作用为撑满可用空间.
什么是规则集
在 CSS 中,规则集(Rule Set)是一组定义了样式属性和值的规则的集合,用于定义如何渲染 HTML 或 XML 文档中的元素。每个规则集通常由一个选择器和一个声明块组成。
简单的 CSS 规则集
h1 {
color: blue;
font-size: 24px;
}
h1 是选择器
,表示应用样式于 HTML 中的<h1> 元素
color
和font-size
是属性
blue 和 24px 是值
,用于设置<h1> 元素
的文本颜色和字体大小 这个规则集
将使所有<h1> 元素
的文本颜色变为蓝色,并将字体大小设置为 24 像素。
// decl 表示当前处理的 CSS 属性声明节点
// { AtRule, Rule } 表示 PostCSS 中的两个辅助类,用于创建新的规则和规则集
function process (decl, { AtRule, Rule }) {
if (decl.value !== '100vh') return
// 获取当前 CSS 属性声明节点的父节点,即所属的规则集。
let rule = decl.parent
// 创建一个新的 @supports 规则,并设置其参数为 (-webkit-touch-callout: none)
// 并将其来源位置(source)设置为当前属性声明节点的来源位置(source)。
let media = new AtRule({
name: 'supports',
params: '(-webkit-touch-callout: none)',
source: decl.source
})
// 将创建的 @supports 规则插入到当前规则集的后面。
rule.after(media)
// 创建一个新的规则集,其选择器和来源位置(source)与当前规则集相同。
let clonedRule = new Rule({
selector: rule.selector,
source: rule.source
})
// 将新创建的规则集作为子节点添加到 @supports 规则中。
media.append(clonedRule)
// 将一个新的属性声明添加到新创建的规则集中,其中属性为当前属性声明节点的属性名,值为 '-webkit-fill-available'
// 并将来源位置设置为当前属性声明节点的来源位置。
clonedRule.append({
prop: decl.prop,
value: '-webkit-fill-available',
source: decl.source
})
}
module.exports = () => {
return {
postcssPlugin: 'postcss-100vh-fix',
Declaration: {
'min-height': process,
'max-height': process,
'height': process
}
}
}
module.exports.postcss = true