H5项目踩坑及出坑实践

2020 年 9 月 27 日

H5项目踩坑及出坑实践

相比于 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 时)


><meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=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()。设置如下:


body {    padding-top: constant(safe-area-inset-top);    padding-top: env(safe-area-inset-top);//为导航栏+状态栏的高度 88px    padding-left: constant(safe-area-inset-left);    padding-left: env(safe-area-inset-left); //如果未竖屏时为0     padding-right: constant(safe-area-inset-right);//如果未竖屏时为0    padding-right: env(safe-area-inset-right);    padding-bottom: constant(safe-area-inset-bottom));//距离底部圆弧的距离34px    padding-bottom:  env(safe-area-inset-bottom));}
复制代码


通过上述设置,可以开辟出适配 iPhoneX 系列手机的安全区域。


在实际应用中,为了解决底部出现文字遮挡、fixed 按钮不可点击,或者呈现黑底或白底空白区域的问题,同时适配不同的宽高比。结合媒体查询分别适配 X,XS MAX ,XR,给底部 fixed 的元素加一个适配底部小黑条和圆角的底部高度,如下面 fixed-footer,会出现底部 body 超出底部 fixed 部分的问题,可以给 body 加一句<div class=“footer”></div>


,使得每个 X 的屏幕都有一个 div 块,把内容顶上去,防止出现底部透传现象。


//iphone X@media only screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) {    .fixed-footer{    bottom: constant(safe-area-inset-bottom) ;    bottom:  env(safe-area-inset-bottom);    }    .footer {    position: fixed;    bottom: 0;    width: 100%;    height: constant(safe-area-inset-bottom)    height: env(safe-area-inset-bottom)    background-color: #fff;}}//iphone Xs Max@media only screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio:3) {    .fixed-footer{    bottom:  constant(safe-area-inset-bottom)    bottom: env(safe-area-inset-bottom)    }    .footer {    position: fixed;    bottom: 0;    width: 100%;    height: constant(safe-area-inset-bottom)    height:  env(safe-area-inset-bottom)    background-color: #fff;    }}//iphone XR@media only screen and (device-width: 414px) and (device-height:     896px) and (-webkit-device-pixel-ratio:2) {    .fixed-footer{    bottom: constant(safe-area-inset-bottom)    bottom: env(safe-area-inset-bottom)    }    .footer {        position: fixed;        bottom: 0;        width: 100%;        height:  constant(safe-area-inset-bottom)        height:  env(safe-area-inset-bottom)        background-color: #fff;     }}
复制代码


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. 禁止缩放


<meta name="viewport" content="width=device-width, user-scalable=no">// 关键是user-scalable=no
复制代码


但是在 iOS10 下面及部分 UC 浏览器中为了提高网站的辅助功能那个,屏蔽了 Meta 下的 user-scalable=no 功能。就算加上 user-scalable=no,浏览器也能支持手动缩放。可以用 js 加监听事件来阻止手动缩放。代码如下:


   window.onload=function () {          document.addEventListener('touchstart',function (event) {              if(event.touches.length>1){                  event.preventDefault();              }          })          var lastTouchEnd=0;          document.addEventListener('touchend',function (event) {              var now=(new Date()).getTime();              if(now-lastTouchEnd<=300){                  event.preventDefault();              }              lastTouchEnd=now;          },false)      } 
复制代码


b. 用 touch 事件替代 click 事件


原理就是:


  • 当我们手指触摸屏幕,记录当前触摸时间

  • 当我们手指离开屏幕,用离开的时间减去触摸的时间

  • 如果时间小于150ms,并且没有滑动过屏幕,那么我们就定义为点击


//封装tap,解决click 300ms延时function tap(obj,callback){  var flag = false;  var startTime = 0;  //记录触摸时候的时间变量  obj.addEventListener('touchstart',function(e){   startTime = Date.now() });  obj.addEventListener('touchmove',function(e){   flag = true; //看看是否有滑动,有滑动算拖拽,不算点击 });  obj.addEventListener('touchend',function(e){    if(!flag && (Date.now() - startTime) < 150){ //如果手指触摸和离开时间小于150ms算点击      callback && callback() //执行回调函数   }   flag = false;  //取反,重置   stratTime = 0; });}
复制代码


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;//让鼠标点击事件失效。
复制代码


蒙层隐藏后,给按钮下面元素添上 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。现在做移动端开发时一般都要加上一句话:


&lt;meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
复制代码


这句话定义了本页面的 viewport 的宽度为设备宽度,初始缩放值和最大缩放值都为 1,并禁止了用户缩放。


viewport 的设置和屏幕物理分辨率是按比例而不是相同的,移动端 window 对象有个 devicePixelRatio 属性,它表示设备物理像素和 css 像素的比例,在 retina 屏的 iphone 手机上,这个值为 2 或 3, css 里写的 1px 长度映射到物理像素上就有 2px 或 3px。通过设置 viewport,可以改变 css 中的 1px 用多少物理像素来渲染,设置了不同的 viewport,当然 1px 的线条看起来粗细不一致。


解决方案


a. 在公共样式里面定义一个类,使用伪元素+绝对定位+scale,优点:兼容性较好,缺点:input 元素不支持伪元素


<div class="wrap">    内容区域</div>
复制代码


设置四周的边框:


.wrap        height: 40px;        position: relative;        &::after             content: "";            position: absolute;            top: 0;            left: 0;            width: 200%;            height: 200%;            transform-origin: 0 0; -webkit-transform-origin: 0 0; -moz-transform-origin: 0 0; -o-transform-origin: 0 0;            transform: scale(.5); -webkit-transform: scale(.5); -moz-transform: scale(.5); -o-transform: scale(.5);            border: 1px solid #ebebf0;
复制代码


b.使用 rem 改进


使用 rem 作为单位,这样可以更好地去实现移动端的响应式像素以及 Retina 屏幕上的表现。优点是实现简单,缺点是部分机型还是不兼容。


c. css 中引入 svg 改进


具体思路是为元素加上 background-image,然后把 svg 置为图片类型,因为 svg 上的 1px 就是实实在在的只占 1 个物理像素。实现很简单,代码如下:


input {  background-image:url("data:image/svg+xml;base64,<svg xmlns='http://www.w3.org/2000/svg' width='100%' height='100%'><line x1='0' y1='100%' x2='100%' y2='100%' stroke='#dcdcdc' stroke-width='1'/></svg>");}
复制代码


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 定位的元素。导致可视区域变小,布局错乱。


解决方案


软键盘将页面顶起来的解决方案,主要是通过监听页面高度变化,强制恢复成弹出前的高度。


// 记录原有的视口高度const originalHeight = document.body.clientHeight || document.documentElement.clientHeight;window.onresize = function(){  var resizeHeight = document.documentElement.clientHeight || document.body.clientHeight;  if(resizeHeight < originalHeight ){    // 恢复内容区域高度    // const container = document.getElementById("container")    // 例如 container.style.height = originalHeight;  }}
复制代码


键盘不能回落问题出现在 iOS12+和 wechat6.7.4+中,而在微信 H5 开发中是比较常见的 Bug。兼容原理:1.判断版本类型 2.更改滚动的可视区域。


const isWechat = window.navigator.userAgent.match(/MicroMessenger\/([\d\.]+)/i);if (!isWechat) return;const wechatVersion = wechatInfo[1];const version = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/);

// 如果设备类型为iOS12+和wechat6.7.4+,恢复成原来的视口if (+wechatVersion.replace(/\./g, '') >= 674 && +version[1] >= 12) { window.scrollTo(0, Math.max(document.body.clientHeight, document.documentElement.clientHeight));}window.scrollTo(x-coord, y-coord),其中window.scrollTo(0, clientHeight)恢复成原来的视口
复制代码


6 总结


H5 项目有的坑远不止这些,出坑解决方案更是个人有个人的偏好。后续会持续输出相关踩坑出坑方案。生命不息,踩坑不止…


7 推荐文章



本文转载自公众号(ID:)。


原文链接


H5项目踩坑及出坑实践


2020 年 9 月 27 日 10:001498

评论

发布
暂无评论
发现更多内容

2020第十三届南京国际智慧工地装备展览会

InfoQ_caf7dbb9aa8a

“三段三域法”应用架构模型

异想的芦苇

架构 架构设计 技术架构

MySQL-技术专题-实战技巧

李浩宇/Alex

晨间日记的奇迹

熊斌

读书笔记

架构方法论之“极限审视法”

异想的芦苇

架构 方法论 设计思维

手把手教你锤面试官 04——假装精通redis

慵懒的土拨鼠

MySQL-技术专题-Join语法以及性能优化

李浩宇/Alex

坚持写技术博客一年能有多少收获!

小傅哥

Java 面试题 架构师 编程经验 技术博客

技术革新的脉络及趋势

异想的芦苇

技术 进步

转型敏捷123

技术管理Jo

轻言业务架构图

异想的芦苇

架构 企业架构 架构设计 架构设计原则 业务架构

技术解码 | 玩转视频播放,自适应码流技术

腾讯云视频云

音视频 转码

我就不服了,看完这篇文章,5大常见消息队列开发你还学不会

小Q

Java 编程 程序员 开发 消息队列

SpringBoot-技术专题-@Async异步注解

李浩宇/Alex

架构师训练营第一期 - 第四周课后 - 作业二

极客大学架构师训练营

PanDownload复活了!60MB/s!附下载地址

程序员生活志

PanDownload 网盘 下载器

第3周作业提交

饭桶

2020第十三届南京国际智慧新零售暨无人售货展览会

InfoQ_caf7dbb9aa8a

2020第十三届南京国际大数据产业博览会

InfoQ_caf7dbb9aa8a

从戚家军看组织战斗力塑造(组织的六脉神剑)

异想的芦苇

组织

Java 客户端操作 FastDFS 实现文件上传下载替换删除

哈喽沃德先生

Java 文件系统 分布式文件存储 fastdfs 文件服务器

高难度对话读书笔记——目的篇

wo是一棵草

2020第十三届南京国际智慧停车展览会

InfoQ_caf7dbb9aa8a

数字货币交易所系统开发源码,交易平台搭建

WX13823153201

数字货币交易所系统开发

MySQL-技术专题-SQL性能分析

李浩宇/Alex

Redis-技术专题-数据结构

李浩宇/Alex

全屋智能2020第十三届(南京)国际智能家居展览会

InfoQ_caf7dbb9aa8a

2020南京国际工业互联网及工业通讯展览会

InfoQ_caf7dbb9aa8a

2020南京国际人工智能产品展览会

InfoQ_caf7dbb9aa8a

人工智能

什么是 Kubeless?| 玩转 Kubeless

donghui2020

Kubernetes kubeless

第3周学习总结

饭桶

H5项目踩坑及出坑实践-InfoQ