引言
笔者是一名前端开发工程师,大部分开发工作都是在 H5 的世界里遨游。多年的 H5 开发经验总结了一些常见的兼容性问题,在这里分享给大家,希望在遇到同类问题时能让你少走弯路,留住日益蓬松的头发。
注:这里不会涉及 css 厂商前缀的兼容问题,此内容会在前端工程化篇中去讲解。
Android 相关
使用 line-height 实现文字垂直居中,发现文字偏上
解决方案:采用 flex 布局,align-items: center 来替代,兼容性更高
.elem {
display: flex;
justify-content: center;
align-items: center;
}
border-radius 画出的圆在移动端有毛边
解决方案:给元素添加 overflow: hidden 属性
.elem {
overflow: hidden;
}
安卓上去掉语音输入按钮
input::-webkit-input-speech-button {
display: none;
}
iOS 相关
Vue 单页应用在 iOS 上微信分享失效,图片,标题和描述均未正常显示,安卓上分享正常
iOS 分享 H5 的效果(失效)
安卓分享 H5 的效果(正常)
原因分析:我们一般在 APP.vue 的 mounted 生命周期中初始化微信 SDK,此时页面的地址 hash 是#/,而首页的 hash 是#/home,导致初始化微信 SDK 时传入的分享 url 和用户实际触发分享操作时页面的 url 不一致,致使在 iOS 上分享失败。
解决方案:初始化微信分享 SDK 时传入的地址,和实际触发分享时页面的地址保持一致
iOS safari 被点击元素会出现半透明灰色遮罩
解决方法:给 html 或者 body 加如下 css 代码
body {
-webkit-tap-highlight-color: rgba(0,0,0,0);
-webkit-user-modify: read-write-plaintext-only;
}
iOS 禁止保存或拷贝图像
长按图片保存场景下,禁止 IOS 默认识别图像行为
img {
-webkit-touch-callout: none;
}
iOS 端微信 H5 页面上下滑动时卡顿
解决方法:给滚动元素加上-webkit-overflow-scrolling 属性
-webkit-overflow-scrolling 属性是来控制元素在移动设备上是否有回弹的效果
- auto:使用普通滚动,当手指在屏幕上离开时,滚动立即停止
- touch:使用具有回弹效果的滚动, 当手指从触摸屏上移开,内容会继续保持一段时间的滚动效果。继续滚动的速度和持续的时间和滚动手势的强烈程度成正比。同时也会创建一个新的堆栈上下文。
body {
-webkit-overflow-scrolling:touch;
}
iOS 默认输入框内阴影重置
阻止 iOS 默认的美化页面的策略-webkit-appearance:none;
input {
border: 0;
-webkit-appearance:none;
}
对非可点击元素(div,span 等)监听 click 事件,部分 ios 版本不会触发事件
解决方案 1:添加 css 属性 cursor: pointer; 解决方案 2:换成 button 元素
cursor: pointer;
<button></button>
一进页面就调用 focus()方法或者设置 autofocus 属性,输入框没有自动聚焦
无解,在 iOS 系统中自动聚焦不被允许
将日期(yyyy-mm-dd HH:mm:ss)转换成时间戳报错
const date = '2022-09-22 12:00:00';
// 下面写法iOS会报错无法解析
const timeStamp = new Date(date).getTime();
// 兼容写法如下,将横杠转换成斜杠
const timeStamp = new Date(date.replace(/\-/g, '/')).getTime();
webview 相关
手机底部刘海存在背景,和页面背景色不一致
通过指定 body 的背景色来解决。
body {
background-color: #fff;
// or 暗色模式
// background-color: #000;
}
对于带有 hash 的 H5 链接,部分手机厂商的 webview 打开 H5 页面会加载两次
这是部分 webview 对于特殊 url 有独特的解析和加载逻辑,去掉 hash 即可
// 比如下面这个单页应用的链接,完全可以去掉尾部的 hash
https://vivo.diyring.cc/friend/5b818e2a0b8b6113#/
音视频问题
audio 和 video 自动播放问题
由于自动播放网页中的音频或视频,会给用户带来一些困扰或者不必要的流量消耗,所以苹果系统和安卓系统通常都会禁止自动播放和使用 JS 的触发播放,必须由用户来触发才可以播放。
软键盘问题
输入框聚焦,页面底部内容被动往上推
原因分析:这种情况出现在 Android 手机,输入框聚焦弹出输入法时,会触发页面的 resize 事件。页面的可视区域被缩小,导致页面内容被动往上移动。 解决方案:将页面最外层设置为绝对定位,宽高均为 100%,将滚动容器的父节点设一个最小高度为页面的初始化高度。
<template>
<html>
<head></head>
<body>
<div id="app">
</div class="view">
<div class="scroll">
<input type="tel" placeholder="请输入手机号码" :maxlength="11" />
</div>
</div>
</div>
</body>
</html>
</template>
<script lang="ts">
{
...
// 页面高度信息
@Getter('innerHeight') innerHeight: string;
// 更新页面高度信息
@Mutation('UPDATE_INNER_HEIGHT') updateInnerHeight: (height: number) => void;
mounted() {
// 进入页面就获取页面高度快照,给页面设置一个最小高度,防止输入框聚焦后顶起页面底部内容
this.updateInnerHeight(window.innerHeight);
window.addEventListener('resize', this.adjustInnerHeight);
}
// iPhone进入二级页底部会出现导航条影响innerHeight的取值
adjustInnerHeight() {
// 差值>200,我们认为是输入框聚焦弹起输入框导致的resize,不更新innerheight
const isEffective = Math.abs(window.innerHeight - this.innerHeight) < 200;
isEffective && this.updateInnerHeight(window.innerHeight);
}
...
}
</script>
<style lang="less">
#app {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.view {
overflow: hidden;
height: 100%;
}
.scroll {
overflow-y: scroll;
height: 100%;
}
</style>
其它问题
body 存在默认背景色
body 标签在大部分浏览器中的默认背景色是白色,但在极少数浏览器中的背景颜色是淡绿色或者其他颜色。
解决方案:通过指定 body 背景色为#fff,来兼容更多设备。
body {
background-color: #fff;
}
旋转屏幕的时候,字体大小调整的问题
body {
-webkit-text-size-adjust: 100%;
}
页面回退
安卓回退到上一页,上一页会触发 reload 事件,重新加载页面;
iOS 回退到上一页,会触发 visibilitychange 事件,不会重载页面。
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
backgroundMusic.play();
} else {
backgroundMusic.pause();
}
});
我们可以在 iOS 上监听该事件,处理页面回退逻辑中的系统差异问题。
html2canvas 插件生成截图有黑边
解决方法:降低html2canvas版本至1.0.0-rc.3
// 想要保存的图片节点
const dom = canvasRef.value as HTMLElement;
document.getElementById('share');
html2canvas(dom, {
allowTaint: true,
useCORS: true,
logging: true,
windowWidth: window.innerWidth,
windowHeight: window.innerHeight,
ignoreElements: elem => {
return elem && elem.className.includes('desc');
},
}).then((canvas: HTMLCanvasElement) => {
// canvas 转化为图片
const img = canvas.toDataURL('image/jpeg');
const image = new Image();
image.src = img;
image.classList.add('img');
const container = document.getElementById('picture');
const child = document.querySelector('#picture img');
child && container?.removeChild(child);
document.getElementById('picture')?.appendChild(image);
});