H5在全屏Webview中双端适配刘海屏
背景:
最近遇到一个看似常规的 H5 需求,是 App 内嵌的一个功能模块,看样子跟往常一样重复造轮子就 OK 了,客户端开个 Webview 加载页面即可。
正常我们遇到最多的是下面这种类型:
这种的话一般是封装一个 Webview 包含返回+标题+分享功能,然后加载 H5 即可,返回即关闭 Webview,标题是读取网页的 Title 属性,分享是调起客户端的分享弹窗。
然是这次的 H5 有点不寻常的东西:
- 导航栏除了返回键、title、右侧的操作菜单(进入另一个 H5 页),在 title 还有一个操作项 ❔,用于点击弹出说明框。
- 有一个穿透状态栏和导航栏的背景
大概长下面这样:
向上面的这种复杂页面一般是客户端做的,但是!!!
因为种种原因,最后商量用 H5 做。那看到这样的设计图,机智的攻城狮们一般会跟产品争论一番讨论说你这背景图做不了,只能到导航栏以下,并且你这个问号得移到其他地方 bulabulabula。。。
然而作为一名专业的攻城狮,我们当然是奔着最佳的视觉感官+用户体验去的,遇到问题要克服之!
那么确定 100%还原设计图后,首先想到的一个方法是和双端定义一系列协议,包括设置全屏背景图、在 title 后面加操作按钮(及隐藏方法,因为到其他页面就没了),在右侧加自定义的菜单,点击后可跳转其他页面。这个看着就很麻烦,涉及到一系列的交互想想就头疼,还不如直接原生写的痛快一点。和客户端你同学讨论后他们果然面露难色,在我说完第二秒就否决的这种做法,原因很简单:
1、交互太复杂
2、扩展性太差,下次 H5 的设计换个样子又得加新交互
最终决定采用全屏 Webview 的形式,整个页面交给 H5 控制,这样不管页面设计成什么样都能实现,什么全屏背景,设么导航啦加各种东西通通不在话下。于是愉快的开发开始了。
然鹅在打码到一半的时候我意识到一个问题:双端的状态栏高度不一致,并且现在还有刘海屏存在 🌚 这可肿么办?
一、IOS 适配
首先对于 IOS 来说,乔帮主整的还是比较规范的,毕竟 IOS 闭源系统只能跑在 Apple 硬件上,设备型号有限,已知各机型尺寸如下:
注: 这里获取到的 px 值跟 web 中的 px 虽然单位一样,但并不是我们需要的值!!!Web 所需的 px 实际为 IOS 中的 pt 值…,px 转 pt 需要根据设备的 ppi(Pixels Per Inch: 像素密度)进行转换:
px: pixel 像素,是屏幕上的显示的基本点,他并不是长度单位,这个点可以很大,也可以很小。点小的话就很清晰,我们称之为“分辨率高”,反之就是“分辨率低”。所以像素是一个相对单位。
pt: point 准确的说法是一个专用印刷单位“镑”,大小为 1/72 英寸,是一个长度单位。也是绝对长度。
可以看到 ios 中的 px 转 pt 根据设备的 ppi 大概是 3:1/2:1/1:1 转换。
转换完可以看到:
- 4.7 寸 6、6s、7、8,状态栏高度为 20pt,导航栏高度为 44pt.
- 5.5 寸的 6p、6sp、7p、8p,状态栏高度为 18pt,导航栏高度为 44pt.
- 拥有刘海屏的 X、XR、XS、XS MAX、11 等一系列刘海屏,状态栏高度为 44pt,导航栏高度为 44pt.
不难发现:
- 导航栏 高度所有机型都为 44pt;
- 状态栏 高度大致可以根据是否为刘海屏分为两类。没有刘海屏的大小机型分别为 18 和 20pt,可以近似的看成都是 20pt 来处理,问题不大,有刘海屏的则统一为 44pt 高,跟导航栏高度相同。
适配方案:
iOS 端的适配方案有两种:Apple 官方适配方案、机型区分适配、jsBridge 方案
Apple 官方适配方案:
1、在粪叉之后引入了一个新概念:“safe area(安全区域)”,安全区域指屏幕内不受圆角、齐刘海、底部小黑条等元素影响的可视窗口。如下图:
2、同时,从 iOS11 开始,为了适配刘海屏,Apple 公司对 HTML 的 viewport meta 标签做了扩展
1 | <meta name="viewport" content="viewport-fit=cover" /> |
viewport-fit=cover
可设置为auto
, contain
, cover
三种状态,这里我们重点使用cover
值,指页面完全充满屏幕。
3、iOS11 同时新增了一个特性,constant(safe-area-inset-*),这是 Webkit 的一个 CSS 函数,用于获取安全区域与边界的距离,有四个预定义的变量(单位 px):
- safe-area-inset-left:安全区域距离左边界距离,横屏时适配
- safe-area-inset-right:安全区域距离右边界距离,横屏时适配
- safe-area-inset-top:安全区域距离顶部边界距离,竖屏下刘海屏为 44px,iphone6 系列 20px,竖屏刘海适配关键
- safe-area-inset-bottom:安全区域距离底部边界距离,竖屏下为 34px,竖屏小黑条适配关键
这样适配方案就比较明确了:
- 首先通过设置
<meta name="viewport" content="viewport-fit=cover">
让页面充满全屏 - 通过 Webkit 内置的 CSS 函数,获取安全区域与各边之间的间距,然后通过 padding/margin/绝对定位等方式,让页面元素展示在安全区域内。
注: Webkit 在 iOS11 中新增 CSS Functions: env( )替代 constant( ),文档中推荐使用 env( ),而 constant( ) 从 Safari Techology Preview 41 和 iOS11.2 Beta 开始会被弃用。在不支持 env( )的浏览器中,会自动忽略这一样式规则,不影响网页正常的渲染。为了达到最大兼容目的,我们可以 constant( ) 和 env( ) 同时使用。
1 | padding-top: constant(safe-area-inset-top); /* iOS 11.0 */ |
最终适配代码如下:
使用@supports
查询机型是否支持constant() / env()
实现兼容代码隔离,个别安卓也会成功进入这个判断,因此加上-webkit-overflow-scrolling: touch
的判断可以有效规避安卓机。
1 | @supports ( |
机型区分适配
这个就比较简单粗暴无脑了。因为目前市面上已有的 Apple 手机尺寸我们都是已知的,那剩下的就是 css 中的 media 适配了:
1 | /* iphone x / xs / 11 pro*/ |
emmmmmm… 工作量大了点,另外每年 9 月份发布会后要及时更新代码 🙈
jsBridge 方案
如果你跟客户端小哥哥的关系比较好的话,就用这种方案吧 😳,让客户端写个方法获取状态栏高度,然后在页面加载的时候通过 jsbridge 调用获取到状态栏高度,然后设置页面样式即可。
好了,鄙人想到的 iOS 适配方案到此为止。
二、Android 适配方案
整完相对规范的 iOS,开源的 Android 就相当眼花缭乱了,机器厂商百花齐放,各厂商的机型也是眼花缭乱。Android 机型成百上千,适配方案反而变的简单了!!!why? 因为只有一种方案:JSBridge
像上述 iOS 的适配方案中,官方适配方案 Android 肯定是么得了,毕竟机型太多,搞不了官方规范。其次就是 CSS media 查询精准适配,如果你的应用只针对于少数机型,那这种方案还是可以用用的,倘若不是那就拜拜了您嘞。
jsBridge 方案
同 iOS,客户端获取状态栏高度后,H5 通过 JSBridge 交互拿到状态栏高度,设置页面样式避开齐刘海区域。
参考:
H5在全屏Webview中双端适配刘海屏