关注前沿技术,分享热点话题,QCon全球软件开发大会三站同启,重磅回归!立即查看 了解详情

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:00 1310

评论

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

合约跟单软件开发,合约跟单交易所系统开发搭建

13530558032

OFD版式技术深度解读:卷首语

华宇法律科技

版式文档 OFD

新基建迎来风口 新人才仍有缺口

CECBC区块链专委会

人工智能 新基建 数字化基础

Week12

一叶知秋

数字人民币钱包短暂露面 金融诈骗伺机而起

CECBC区块链专委会

数字货币 钱包 货币

数字货币交易平台搭建,去中心化交易所开发方案

13530558032

人民版权 获2020中国产业区块链创新奖

CECBC区块链专委会

区块链 产业发展 版权

数字资产钱包开发,深圳区块链理财钱包服务商

13530558032

Python 到底是强类型语言,还是弱类型语言?

Python猫

Java c++ Python 编程

管理时间还是挥霍时间?

钰湚

时间管理 工作体会 工作哲学 学习法

在面试中成长

escray

面试 学习笔记 面试现场

MySQL复杂where条件分析

程序员历小冰

MySQL

10万奖金等你拿!2020第四届易观OLAP算法大赛火热开启

易观大数据

开发任务管理分析报告

森林

你也许还不懂静态方法和实例方法

架构师修行之路

产品经理的架构思维

吴世亮

架构 产品经理 电商

开发者的福音,LR.NET模块化代码生成器

Learun

Java 敏捷开发 .net core 计算机程序设计艺术 软件设计

看百度技术专家如何深入研究,重复使用的代码经验——设计模式

周老师

Java 编程 程序员 架构 设计模式

易观CTO郭炜:如何构建企业级大数据Ad-hoc查询引擎

易观大数据

深入了解 Rust 异步开发模式

lipi

rust 异步

Vue+Springboot项目部署

ZRK

Vue 前后端分离 springboot 部署

面试是一张窄窄的船票

escray

面试 学习笔记 面试现场

NodeX Component - 滴滴集团 Node.js 生态组件体系

滴滴普惠出行

【译】Amazon Aurora: Design Considerations for High Throughput Cloud-Native Relational Databases 上篇

花里胡哨

分布式数据库 异步 Amazon Aurora 日志驱动

文件系统

Linuxer

JAVA,.NET项目开发难上手?Learun敏捷开发框架解君愁

Philips

Java 敏捷开发 .net core

向云再出发:如数据般飞驰的内蒙古

脑极体

controller-manager的主动驱逐

Geek_f24c45

Kubernetes k8s

USDT承兑商软件开发,区块链支付系统源码搭建

13530558032

Redis 持久化--AOF

是老郭啊

redis redis持久化 aof

Spring Boot中获取配置的一些方法

Geek_416be1

Spring Boot 2

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