相比于 PC 项目只需要关注功能实现,H5 项目兼容性似乎是前端开发和测试童鞋需要重点关注的问题。我做 H5 项目也有一段时间了,下面从自己项目中遇到的问题稍稍做一下复盘,回顾一下踩坑和出坑的过程。
1 iphoneX 系列手机适配问题
表现
头部刘海两侧区域或者底部区域,出现刘海遮挡文字遮挡、点击区域,或者呈现黑底或白底空白区域。
产生原因
iPhoneX 及以上版本手机都采用了状态栏、圆弧展示角、传感器槽、主屏幕指示器和屏幕边缘手势(具体名词注释看下图)。头部底部侧边栏都需要做特殊处理,使得 content 尽可能的处于安全区域内,适配 iPhoneX 系列手机的特殊性。
解决方案
设置安全区域,填充危险区域,危险区域不做操作和内容展示。何为安全区域(safe Area),顾名思义,安全区域即为正常显示内容的区域,但该区域不受状态栏和其它内容影响。当界面显示在屏幕上时,安全区域即为导航栏、选项卡栏、工具栏和其他父视图不覆盖的屏幕视图的一部分。
具体操作
Step1:viewport-fit
viewport-fit meta 标签设置为 cover,获取所有区域填充。判断设备是否属于 iPhone X,给头部底部增加适配层 。viewport-fit 有 3 个值,分别为:
auto:此值不影响初始布局视图端口,并且整个web页面都是可查看的。
contain:视图端口按比例缩放,以适合显示内嵌的最大矩形。
cover:视图端口被缩放以填充设备显示。强烈建议使用safe area inset变量,以确保重要内容不会出现在显示之外。
viewport-fit meta 标签设置(cover 时)
Step2:增加适配层
WebKit 包含了新的 CSS 函数 constant()和 env(),以及一组四个预定义的常量:safe-area-inset-left, safe-area-inset-right, safe-area-inset-top 和 safe-area-inset-bottom。当合并一起使用时,允许样式引用每个方面的安全区域的大小。
当我们设置 viewport-fit:contain,也就是默认的时候时;设置 safe-area-inset-left, safe-area-inset-right, safe-area-inset-top 和 safe-area-inset-bottom 等参数时不起作用的。只有设为 cover 才可以用 contant()和 env()方法。
当我们设置 viewport-fit:cover 时,为了达到向前兼容 ios11.2 以前的版本向后兼容 ios11.2 以后版本的浏览器,需要同时用 contant()和 env()。设置如下:
通过上述设置,可以开辟出适配 iPhoneX 系列手机的安全区域。
在实际应用中,为了解决底部出现文字遮挡、fixed 按钮不可点击,或者呈现黑底或白底空白区域的问题,同时适配不同的宽高比。结合媒体查询分别适配 X,XS MAX ,XR,给底部 fixed 的元素加一个适配底部小黑条和圆角的底部高度,如下面 fixed-footer,会出现底部 body 超出底部 fixed 部分的问题,可以给 body 加一句<div class=“footer”></div>
,使得每个 X 的屏幕都有一个 div 块,把内容顶上去,防止出现底部透传现象。
2 click 点击延迟与穿透问题
表现
延时:点击某个滚动的动画(如图所示),交互中动画会停止,出现下一步操作。但是在 IOS 系统中,点击没有反应,与 Android 效果差别很大。
穿透:点击蒙层,蒙层消失后发现触发了蒙层下层元素点击事件。或者点击页内按钮跳转至新页,发现新页的对应位置的 click 事件被触发了。
产生原因
为什么会出现 click 延时?
iOS 中的 safari,为了实现双击缩放操作,在单击 300ms 之后,如果未进行第二次点击,则执行 click 单击操作。也就是说来判断用户行为是否为双击缩放产生的。后来其他的浏览器都效仿 safari,实现了双击缩放功能,导致在大部分 app 中无论是否需要双击缩放这种行为,click 单击都会产生 300ms 延迟。
为什么会出现点击透传?
当点击移动设备的屏幕时, 可以分解成多个事件,顺序依次为:touchstart — touchmove — touchend — click, 这些事件是按顺序依次触发的。双层元素叠加时,在上层元素上绑定 touch 事件,下层元素绑定 click 事件。由于 click 发生在 touch 之后,点击上层元素,元素消失,此时事件只进行到 touchend,300ms 后下层元素会触发 click 事件,由此产生了点击穿透的效果。当然对于跨页面点击穿透问题,和上述原理差不多,同时满足了 touch,跳转新页面,click 事件,三者缺一不可。
解决方案
解决 click 延时:
a. 禁止缩放
但是在 iOS10 下面及部分 UC 浏览器中为了提高网站的辅助功能那个,屏蔽了 Meta 下的 user-scalable=no 功能。就算加上 user-scalable=no,浏览器也能支持手动缩放。可以用 js 加监听事件来阻止手动缩放。代码如下:
b. 用 touch 事件替代 click 事件
原理就是:
当我们手指触摸屏幕,记录当前触摸时间
当我们手指离开屏幕,用离开的时间减去触摸的时间
如果时间小于150ms,并且没有滑动过屏幕,那么我们就定义为点击
c. 引用 faskclick 插件库
使用 faskclick 库以后,click 延时和穿透问题都可以解决了。至于 faskclick 插件库的实现原理,以后再做总结。
d. vue 项目可以安装 vue-tap 插件
使用方法类 vue 的指令,在本次问题中用的就是这种方案解决的。
解决点击穿透:
a. 经常用的就是不要混用 touch 和 click,把所有的 click 事件替换成 click 事件,但是需要特别注意 a 标签,a 标签的 href 也是 click,需要去掉换成 js 控制的跳转,或者直接改成 span+tap 控制跳转。如果要求不高,不在乎滑走或者滑进来触发事件的话,span+touchend 就可以了,毕竟 tap 需要引入第三方库。当然对交互要求不高的情况下可以全部用 click 事件,但是要想好这 300ms 的后果。
b. 应用 pointer-events。常言道能用 css 解决的问题就不要用 js。pointer-events 是 CSS3 的一个属性,支持的值非常多,其中大部分都是和 SVG 有关。对于点击穿透了解一个 none 就可以了。
蒙层隐藏后,给按钮下面元素添上 pointer-events: none;样式,让 click 穿过去,300ms 后去掉这个样式,恢复响应即可。但是要注意蒙层消失后的的 300ms 内,用户可以看到按钮下面的元素点击没反应,如果用户手速很快的话一定会发现,不推荐使用。
c. 比较推荐的方式如果不介意多加载几 KB 的话,可以尝试上述解决点击延时的 fastclick 库。这里不多加解释说明,具体可以去看 fastclick 库源码。
3 1px 问题
表现
在做 H5 页面时,有时候 UI 稿会出现边框宽度为 1px,如果简单粗暴的写 border:1px solid #eee,UI 在审查的时候也常常会觉得分割线或者边框线太粗了,要更细一点,但是看代码发现也写了 1px。这个时候想改成 0.5px,会发现在很多 IOS7 及以下以及一些 Android 机型上不支持 0.5px。为了解决 1px 变粗问题,我们就要找到一种实现 0.5px 的方案。
产生原因
要知道问题的原因首先要了解一下几个概念:
(1) 物理像素(physical pixel)
一个物理像素是显示器(手机屏幕)上最小的物理显示单元(像素颗粒),在操作系统的调度下,每一个设备像素都有自己的颜色值和亮度值。如:iPhone6 上就有 750*1334 个物理像素颗粒。
(2) 设备独立像素(density-independent pixel)
设备独立像素,也叫密度无关像素,可以认为是计算机坐标系统中得一个点,这个点代表一个可以由程序使用的虚拟像素(比如:css 像素),有时我们也说成是逻辑像素。然后由相关系统转换为物理像素。所以说,物理像素和设备独立像素之间存在着一定的对应关系,这就是接下来要说的设备像素比。
(3) 设备像素比(device pixel ratio )
简称 dpr 设备像素比(简称 dpr)定义了物理像素和设备独立像素的对应关系。它的值可以按如下的公式的得到:
设备像素比(dpr)=物理像素/逻辑像素(px) // 在某一方向上,x 方向或者 y 方向,下图 dpr=2
知道了设备像素比,我们就大概知道了 1px 线变粗的原因。简单来说就是手机屏幕分辨率越来越高了,同样大小的一个手机,它的实际物理像素数更多了。因为不同的移动设备有不同的像素密度,所以我们所写的 1px 在不同的移动设备上等于这个移动设备的 1px。现在做移动端开发时一般都要加上一句话:
这句话定义了本页面的 viewport 的宽度为设备宽度,初始缩放值和最大缩放值都为 1,并禁止了用户缩放。
viewport 的设置和屏幕物理分辨率是按比例而不是相同的,移动端 window 对象有个 devicePixelRatio 属性,它表示设备物理像素和 css 像素的比例,在 retina 屏的 iphone 手机上,这个值为 2 或 3, css 里写的 1px 长度映射到物理像素上就有 2px 或 3px。通过设置 viewport,可以改变 css 中的 1px 用多少物理像素来渲染,设置了不同的 viewport,当然 1px 的线条看起来粗细不一致。
解决方案
a. 在公共样式里面定义一个类,使用伪元素+绝对定位+scale,优点:兼容性较好,缺点:input 元素不支持伪元素
设置四周的边框:
b.使用 rem 改进
使用 rem 作为单位,这样可以更好地去实现移动端的响应式像素以及 Retina 屏幕上的表现。优点是实现简单,缺点是部分机型还是不兼容。
c. css 中引入 svg 改进
具体思路是为元素加上 background-image,然后把 svg 置为图片类型,因为 svg 上的 1px 就是实实在在的只占 1 个物理像素。实现很简单,代码如下:
4 position fixed 和 sticky 兼容性
表现
在如下图所示的图中,当页面滑动到搜索框下面,二手房 tab 会自动吸顶,但是在某些安卓机的原生浏览器中没有吸顶这个动作。
产生原因
吸顶的动作是用 position:sticky 完成的,但是 Caniuse 上显示 sticky 的兼容性如下:
Sticky 的作用相当于 relative 和 fixed 的结合体,当修饰的目标节点再屏幕中时表现为 relative,当要超出的时候是 fixed 的形式展现。但是由于兼容性问题,在安卓端没有很好地兼容。且它的活动范围只能在父元素内,滚动超过父元素的话,它一样不能吸顶。
解决方案
react 解决方案:使用 react-sticky,通过计算 <Sticky> 组件相对于<StickyContai ner>组件的位置进行工作,如果他出现在视口的外面,将其附加到屏幕的顶部所需要的样式作为参数传递给 render callback,作为 child 传递的函数。
JS 解决方案:通过 cssSupport 判断浏览器的支持情况,如果浏览器支持 sticky,则不做处理,否则通过自定义滚动事件的监听,根据 top 的改变来实现 tab 层 fixed 和 absolute 的转换。
vue 解决方案:可以直接使用 vue-sticky 组件,vue-sticky 实现原理大致与 JS 解决方案差不多。
5 软键盘将页面顶起来、收起未回落问题
表现
在 Android 手机中,点击 input 框时,键盘弹出,将页面顶起来,导致页面样式错乱。失去焦点时,键盘收起,键盘区域空白,未回落。
产生原因
我们在 app 布局中会有个固定的底部。在 Android 一些版本中,输入键盘弹出来,会将解压 absolute 和 fixed 定位的元素。导致可视区域变小,布局错乱。
解决方案
软键盘将页面顶起来的解决方案,主要是通过监听页面高度变化,强制恢复成弹出前的高度。
键盘不能回落问题出现在 iOS12+和 wechat6.7.4+中,而在微信 H5 开发中是比较常见的 Bug。兼容原理:1.判断版本类型 2.更改滚动的可视区域。
6 总结
H5 项目有的坑远不止这些,出坑解决方案更是个人有个人的偏好。后续会持续输出相关踩坑出坑方案。生命不息,踩坑不止…
7 推荐文章
吃透移动端 H5 与 Hybrid|实践踩坑12种问题汇总
基于Vue的移动端h5项目总结
App适配IPhoneX----Safe Area (安全区域)
移动端常见的问题–click点击延时解决方案
https://blog.csdn.net/weixin_46113485/article/details/104567528
移动端click事件延迟300ms解决方案
解决iOS10的Safari下Meta设置user-scalable=no无效的方法
聊聊移动端的适配问题
本文转载自公众号(ID:)。
原文链接:
评论