# 1、项目踩坑

# 1、华为荣耀 自带浏览器 position:fixed 底部被工具栏遮挡 解决办法

head中添加

<meta name="x5-fullscreen" content="true">
1

# 2、vue数据变化,视图层没变化, 数据强制更新

this.$forceUpdate()
1

# 2、H5和App的Webview交互

# 2.1、H5通过UA判断是否在webview环境(追加UA)

web页面通过脚本能够很容易的拿到浏览器的ua属性,那么在app启动的时候,自定义添加一个ua属性,那么web页面就能够根据这个自定义的ua属性,轻而易举的判断出是否在app内了。

# 2.2、JS和Native交互(JSBridge)

参考 (opens new window)

Hybrid APP 即混合模式开发的 APP,它可以把低成本、高效率、跨平台的 H5 技术和追求极致用户体验的 Native 技术混合在一起来为用户提供服务。而 Hybrid 技术实施的前提,是实现 H5 和 Native APP 之间的通信,这个实现,我们称之为 JSBridge。

# H5 调用 Native( 2 种方式)

  • 1.理论上,无论是 iOS 还是 Android,提供的 WebView 容器是可以拦截一切 H5 发起的请求的,无论是标准协议(如 http://、https:// 等)还是私有协议(如 weixin:// )。基于这个原理,H5 采用私有协议模拟发起 URL 请求,Native 解析这类 URL 并定制相应的处理函数,这就实现了 H5 调用 Native。
  • 2.在 Native 的开发中,开发者可以给 WebView 容器注入全局变量并挂载在 window 对象上,这样前端 js 就可以通过 window 上全局对象方法 来调用一些 Native 的方法。这里需要注意的是方法注入的时机,一般是 WebView 一旦加载页面就需要注入变量。

# Native 调用 H5

上面提到的给 WebView 容器注入全局变量并挂载在 window 对象上,实际上是 Native 代码执行了一个 evaluateJavaScript 函数,直接运行 js 字符串代码(类似 js 的 eval 函数),从而实现注入。所以 Native 代码也一样可以通过这个 evaluateJavaScript 函数来调用约定好的 js 函数来实现 H5 的调用。

# 2.3、唤起APP

参考地址 (opens new window) github地址 (opens new window)

唤端一般包含了 唤起App下载App 以及 唤起App失败后自动下载 这三个功能。

唤端所使用的技术,通常可以统称为Deep Link(深度链接)技术。不同平台对这项技术有着不同的实现,主流的有这几种:

  • URL Scheme(全平台通用)
  • Universal Link(通用链接,iOS系统专属)
  • App Links 以及衍生的 Chrome Intents(安卓系统专属)

当然,以上只是国际通用标准,我们还需要考虑到“国内特色”,包括但不限于微信爸爸、微博、UC浏览器等这些环境的唤端,对于这些App,主流的唤端方式有这么几种

  • Universal Link(部分App可用,快被国内主流App禁干净了)
  • 微信API launchApplication 和 getInstallState (需要申请白名单,难度很高)
  • 微信开放标签 wx-open-launch-app (微信推荐方式,需要审核,流程较繁琐)
  • 跳转到应用宝,然后通过应用宝唤端 (适用于微信安卓环境)
  • 弹出个蒙层,友好的提示用户,点击右上角按钮,然后选择在浏览器中打开 (easy~)

主要介绍下 URL SchemeUniversal Link微信唤端自家公司App 四种技术场景下的唤端实现

  • URL Scheme:

URL Scheme其实就是一个URL前面的协议部分,比如这个地址 https://m.zhuanzhuan.com,其中 https就是一个Scheme,代表这是一个https的地址。

我们再看一个地址 zhuanzhuan://jump/core/myBuyList/jump?tab=0,当我们访问这个地址的时候,如果系统中有注册 zhuanzhuan:// => 转转App,那么系统便会使用转转App打开这个链接了,接着App会解析URL的path和search部分,执行对应的操作。

那么如何向系统注册 zhuanzhuan 这个Scheme呢,安卓可以在 manifest 里通过 intent-filter 配置,iOS 则可以在 info.plist 文件中添加 URL types 来注册一个 Scheme。

  • Scheme的调用方法:

Scheme的调用也十分简单,我们可以通过 location.hrefiframea标签 来调用 这三种方式并无本质区别,都是让系统访问一个URL,只不过在某些环境下,当方法失效时,需要采用另一种形式。

// SCHEMA_PATH 为 URL Scheme 地址

// location.href 形式
window.top.location.href = SCHEMA_PATH;

// a标签形式
const a = document.createElement("a");
a.setAttribute("href", SCHEMA_PATH);
a.click();

// iframe形式
iframe = document.createElement('iframe');
iframe.frameBorder = '0';
iframe.style.cssText = 'display:none;border:0;width:0;height:0;';
document.body.append(iframe);
iframe.src = SCHEMA_PATH;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • 唤端失败自动下载:

使用URL Scheme会遇到一个棘手的问题,那就是我们无法得知是否成功唤起App。这个也可以理解,因为这个方式本质上就是访问一个URL,并没有特别的地方。

但是唤端失败(通常是用户没有装这个App),我们需要引导用户去下载,这就要求我们需要通过一些手段,去检测唤端是否成功。

常见的做法是通过监听页面是否在 n秒内 隐藏来判断是否成功唤起App,因为如果唤起,那当前页面必然已经退到后台去了。

所以我们处理的流程是这样:

1、当点击唤端后,定时器延时n秒(我们设定的是2.5秒)后执行下载操作
2、监听 visibilitychange 事件,如果页面隐藏,则表示唤端成功,清除定时器
3、如果 visibilitychange 事件没有被触发,那么就代表唤端失败,n秒后就会执行下载的操作

// n秒后执行下载
const timer = setTimeout(() => {
  this.__download(options);
}, options.delay);

// 页面隐藏,那么代表已经调起了app,就清除下载的定时器
const visibilitychange = function() {
  const tag = document.hidden || document.webkitHidden;
  tag && clearTimeout(timer);
};

document.addEventListener("visibilitychange", visibilitychange, false);
document.addEventListener("webkitvisibilitychange", visibilitychange, false);
window.addEventListener(
  "pagehide",
  function() {
    clearTimeout(timer);
  },
  false
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

不过这个方法有个致命问题,就是 n 应该取多少,不同的手机,唤端的时间并不一样。

时间设置太长,用户下载等待太久,会导致用户还没看到下载就退出了。 时间设置太短,可能会导致还没有唤起App,就又同时执行了下载的逻辑。

这里我们经过测试,觉得n设置为2500ms-3000ms,是一个比较合适的值

# 3、移动端常见问题

# 3-1、移动端1px边框问题

参考 (opens new window)

移动端CSS里面写了1px,实际上看起来比1px粗;
UI设计师要求的1px是指设备的物理像素1px,而CSS里记录的像素是逻辑像素。
它们之间存在一个比例关系,可以用javascript中的window.devicePixelRatio来获取,也可以用媒体查询的-webkit-min-device-pixel-ratio来获取。

在手机上border无法达到我们想要的效果。这是因为devicePixelRatio特性导致,iPhone的devicePixelRatio==2,而border-width: 1px描述的是设备独立像素。
所以,border被放大到物理像素2px显示,在iPhone上就显得较粗。

# 解决方案:

  • 1、媒体查询利用设备像素比缩放,设置小数像素
.border { border: 1px solid #999 }
@media screen and (-webkit-min-device-pixel-ratio: 2) {
    .border { border: 0.5px solid #999 }
}
@media screen and (-webkit-min-device-pixel-ratio: 3) {
    .border { border: 0.333333px solid #999 }
}
1
2
3
4
5
6
7
  • 2、媒体查询 + transfrom,对第一个方案的优化
/* 2倍屏 */
@media only screen and (-webkit-min-device-pixel-ratio: 2.0) {
    .border-bottom::after {
        -webkit-transform: scaleY(0.5);
        transform: scaleY(0.5);
    }
}
/* 3倍屏 */
@media only screen and (-webkit-min-device-pixel-ratio: 3.0) {
    .border-bottom::after {
        -webkit-transform: scaleY(0.33);
        transform: scaleY(0.33);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 3-2、H5页面窗口自动调整到设备宽度,并禁止用户缩放页面

<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
1

# 3-3、移动端click屏幕产生300 ms的延迟响应

参考 (opens new window)

# 定义:

当用户一次点击屏幕之后,浏览器并不能立刻判断用户是要进行双击缩放,还是想要进行单击操作。因此,就会等待300毫秒,以判断用户是否再次点击了屏幕。

# 解决方案:

  • 1、通过 meta 标签禁用网页的缩放

  • 2、通过 meta 标签将网页的 viewport 设置为 ideal viewport。

  • 3、FastClick

FastClick 是 FT Labs 专门为解决移动端浏览器 300 毫秒点击延迟问题所开发的一个轻量级的库。简而言之,FastClick 在检测到 touchend 事件的时候,会通过 DOM 自定义事件立即触发一个模拟click事件的自定义事件,并把浏览器在 300 毫秒之后真正触发的 click 事件阻止掉。

# 3-4、iphoneX的“刘海”,底部的安全区域。让页面不能占满屏幕

参考 (opens new window)

# 解决方案:

添加viewport-fit=cover meta标签,使页面占满整个屏幕

<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
1

# 3-5、移动端滚动穿透问题

参考 (opens new window)

# 定义:

移动端弹出fixed弹窗的话,在弹窗上滑动会导致下层的页面跟着滚动,这个叫 “滚动穿透”

# 解决方案:

  • 1、先定义一个变量,用来记录document的scrollTop值
  • 2、在弹窗显示的时候,将最新的document的scrollTop值赋值给之前定义的变量,然后将body设为fixed定位,将body的top值设为之前变量存储的值
  • 3、在弹窗消失的时候,将body设为relative定位,将body的top值设为unset

# Vue Mixin代码:

// mixin.js
export default {
    data() {
        return {
            scrollTop: null // 距离顶端的值
        }
    },
    watch: {
        isShow(newVal) {
            this.stopBodyScroll(newVal)
        }
    },
    methods: {
        stopBodyScroll(isFixed) {
            if (isFixed) {
                this.scrollTop =
                    document.scrollingElement.scrollTop || window.scrollY
                document.body.style.position = 'fixed'
                document.body.style.top = -this.scrollTop + 'px'
            } else {
                document.body.style.position = 'relative'
                document.body.style.top = 'unset'
                window.scrollTo(0, this.scrollTop) // 回到原先的top
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
lastUpdate: 2/14/2025, 2:32:58 PM