《单身娃娃机 2.0》H5 对于我来说是一个比较有纪念意义的项目,因为这是我人生中的第一个10W+

项目来源

《单身娃娃机 2.0》是我要 WhatYouNeed在 2019.11.11 推出的一个 H5 活动。

这是我在 我要 WhatYouNeed 的第一个 H5。(严格来说是第二个,第一个做的《早起立 Flag》H5 因为排期原因被编辑部砍了:<)

之所以是 2.0,是因为去年推出了 1.0 版本,但是由于之前版本的高耦合以及 HardCode,难以继续维护迭代,再加上 2.0 版本又有新的需求,于是经过讨论后就决定重新开始一个全新项目。

刚开始接到这个需求其实压力还蛮大的,毕竟是 150W+ 粉丝量的公众号,需要考虑的问题太多了,一点小差错可能导致非常多的用户会受到影响。

用户体验流程

因为设计这一部分是由设计师完成的,所以在这里就不做分析啦,说一下用户的体验流程。

在《单身娃娃机 2.0》上,用户需要回答一个问题,并录制一段语音,然后便可进入娃娃池,让其他用户抓取。

转化到体验流程的角度来说,具体流程其实就是:

  • 用户进入娃娃机,选择一个问题,并录制一段音频。
  • 用户进入娃娃机开始抓取,控制娃娃机抓取其他娃娃。
  • 抓取成功后可以听到被抓取娃娃用户回答的问题以及录音。
  • 抓取成功的娃娃之间都有一次留言的机会。

涉及到的功能模块

  • 记录用户信息(用到weixin-js-sdk,认证+获取用户信息)
  • 调用录音功能以及上传录音(用到 weixin-js-sdkwx.startRecordwx.uploadVoice
  • 设置微信转发分享的信息(weixin-js-sdkwx.updateTimelineShareDatawx.updateAppMessageShareData
  • 用户状态管理(使用Vuex
  • 摇杆的移动(使用nippleJS

移动端适配布局

VW 移动端适配

在这个项目中,使用了 VW 单位去做了移动端的适配。

vw vh – 相对于 ViewPort(视窗) 的宽度和高度。

具体做法在这里:如何在 Vue 项目中使用 vw 实现移动端适配

在之前做 2018 创行中国全国赛的抽奖 H5 就使用了这套解决方案,根据测试以及用户实际使用反馈的情况来看,还不错。(不考虑 iPad 等大型移动设备的使用情况。)

这套解决方案的好处就是,我可以根据设计师给我的标准原稿( 750 * 1334 )去编写一套 CSS 就好,然后PostCSS等工具会自动帮我转换为 VW 属性,以适配不同尺寸的客户端。

上面解决了 px 到 vw 的转换计算。那么在哪些地方可以使用 vw 来适配我们的页面。
根据相关的测试:

-容器适配,可以使用 vw

-文本的适配,可以使用 vw

-大于 1px 的边框、圆角、阴影都可以使用 vw

-内距和外距,可以使用 vw

1px border 问题

在这个项目中,好在背景颜色都是相同的。

所以这个问题可以通过box-shadow属性很方便的解决:

1
2
3
.out_div {
-webkit-box-shadow: 0 1px 1px -1px rgba(0, 0, 0, 0.3);
}

优化

考虑到活动刚推出时会有高并发的服务器接口请求以及静态资源请求,必须对这个 H5 做最大程度的优化,以保证用户的使用体验。

使用 CDN 加载依赖以减少打包后的体积

之前学习到通过配置 Webpack 的externals项,可以在打包输出时排除掉我们配置的选项。

具体定义如下:

externals 配置选项提供了「从输出的 bundle 中排除依赖」的方法。

所以在该项目中,Webpack 配置如下:

1
2
3
4
5
externals:{
'vue-router': 'Router',
"axios":"axios",
'vue': 'Vue',
}

然后再 index.html 中引入 CDN script:

1
2
3
4
5
<script src="https://cdn.bootcss.com/vue-router/3.0.2/vue-router.min.js"></script>

<script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>

<script src="https://cdn.bootcss.com/vue/2.5.20/vue.min.js"></script>

这是使用了 BootCDN,国内用户经常会请求到这里的 CDN,使用 BootCDN 是为了提高用户缓存的命中率。

关于图片资源

因为这个项目所使用的图片资源并不是很多,体积最大的图片就是背景图,(在保证清晰的同时)压缩后大约为35.6KB,也因为是一屏的 H5,所以也用不到滑动懒加载,以下是该项目使用的处理(压缩)图片的方式。

CSS Sprites

其次是一些小的图片以及 icon(比如娃娃机中的娃娃)则使用了CSS Sprites雪碧图,将 18 张图合并为一张,以减少网络请求,(这里其实花了比较多的时间,为此还专门研究了一番Gulp 和一个名为postcss-lazysprite的插件)

参考链接:
postcss-lazysprite: 一种生成 CSS 雪碧图的懒惰姿势
A PostCSS plugin that automatic generates sprites from the directory of images with hight perfomance.

Base64

图片转 Base64 载Webpack会默认配置,可按情况修改最适合项目的 limit

Webpack 中设置了 limit 为 10240,10KB 及以下图转成 Base64,减少静态资源网络,这个值也不能设置太大,不然会影响渲染效率。

1
2
3
4
5
6
7
8
9
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
// limit:10240,意思为图片大小低于10240Bytes = 10 KB的都会被转成Base64格式,以减少网络请求。
limit: 10240,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},

关于 WebP

本来想考虑使用WebP去进一步压缩背景等图片的大小,但是考虑到目前 WebP 的兼容性问题,(可以点击这里:查看 WebP 的浏览器兼容性),如果要使用 WebP 则需做回退方案,以兼容不支持 WebP 的客户端(低系统版本的 iOS,Android 等)。但是为了赶在双 11(本身 H5 就是属于热点性,再加上后端接口交付时间比较晚),在时间上不容许这套方案,于是放弃。

降低首屏响应时间

!!!百度统计

这里就不得不先说一下百度统计了:这一点就很有趣了,在项目完成、做了部分优化后,开始测试时,并未加入百度统计 JS,在首次打开 H5 是白屏时间都算比较短,都在能接受的范围内。(4G 下不超过 2s)。

结果到最后准备发版,加上了百度统计 JS,发现不对了:

!!!这不是做宝搞吗!一开始以为是电脑微信 IDE 的缘故,但是经过两端移动端(iOS,Android)和 Mac 微信 IDE 以及不同网络情况下测试,都出现了让人难以接受的等待时间!但是为了需要统计数据(公司其他许多产品都用了百度统计,为了数据的功用共用以及在 PM 的强烈要求下),还是继续使用。

解决方案:不得不让设计师在 H5 正是内容开始前加多一个加载动画,以等待 hm.js 完成加载,:

以下是官方文档提供的引用方式:通过 IIFE 生成了一个独立作用域。

1
2
3
4
5
6
7
8
9
<script defer>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement('script');
hm.src = 'https://hm.baidu.com/hm.js?XXXXX';
var s = document.getElementsByTagName('body')[0];
s.appendChild(hm);
})();
</script>

这里修改了几个地方: 将这段 JS 使用了defer属并性放在了 底部(),防止阻塞 DOM 的渲染。

静态资源 GZIP 压缩

因为页面静态资源是直接放在了阿里云的 OSS 对象存储上,OSS 默认对返回内容进行 GZIP 压缩。

这里推荐两篇我当时在查找收藏的觉得比较好的分析文(至少我也能看懂的。。。)

探索 HTTP 传输中 gzip 压缩的秘密

你真的了解 gzip 吗?

缓存

同样,因为使用了阿里云的 OSS,并且配置了 CDN 加速,所以所有静态资源会通过 CDN 进行缓存,以加快资源请求响应速度。

并且有有一点蛮不错的就是:如果更新了静态文件并部署到 OSS 后,OSS 会自动刷新 CDN 上的缓存,从而实现文件更新时缓存自动刷新。(前提是要打开 CDN 缓存刷新功能)

难题

weixin-js-sdk的两端(iOS,Android)兼容问题(invalid signature)

具体表现为:iOS 端只需在 App.vue 中调用一次wx.config后便可在整个示例中调用其他 API,但是在 Android 端则需要在每个页面的 mounted() 中重新调用wx.config注入配置信息才能调用。

首先说说在 Vue 中引入weixin-js-sdk的方式,有两种:

1
2
3
4
5
/* 使用CommonJs规范引入,该项目使用的是这种引入方式 */
const wx = require('weixin-js-sdk');

/* 使用ES6模块引入 */
import wx from 'weixin-js-sdk';

原因

Vue-Router使用了 History 模式,但是 Android 微信客户端不支持 pushState 的 H5 新特性。

微信 JS-SDK 的官方文档中有提到:
所有需要使用 JS-SDK 的页面必须先注入配置信息,否则将无法调用(同一个 url 仅需调用一次,对于变化 url 的 SPA 的 web app 可在每次 url 变化时进行调用,目前 Android 微信客户端不支持 pushState 的 H5 新特性,所以使用 pushState 来实现 web app 的页面会导致签名失败,此问题会在 Android6.2 中修复)。

也就是说,iOS 端在每次切换路由时,SPA 的 url 是不会变的,发起签名请求的 url 参数必须是当前页面的 url 就是最初进入页面时的 url。而 Android 端每次切换路由,SPA 的 url 是会变的,发起签名请求的 url 参数必须是当前页面的 url(而不是最初进入页面时的)

解决方案

iOS 端不变,兼容 Android 端。

1
2
3
4
5
6
7
8
//注册一个全局后置钩子
router.afterEach(() => {
Promise.resolve().then(() => {
getCurrentUrl({
url: global.location.href
});
});
});

通过这种方式异步获取到的 url 就是当前页面的 url 了(而不是之前页面的),也就避免了 invalid signature 问题。

iOS 端、Android 端的区分(引申问题)

关于 iOS 端、Android 端的区分,可以通过navigator.userAgent属性判断,如下:

1
2
3
4
let isiOS = !!navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);

let isAndroid =
navigator.userAgent.indexOf('Android') > -1 || u.indexOf('Adr') > -1;

然后通过commit保存到 Vuex 中。

weixin-js-sdk的分享兼容问题

在测试过程中,解决了wx.config后,又遇到部分 Android 机型无法正常分享的问题,后来发现似乎和微信版本有关。

解决方案

Goole 一番发现有些开发者反应 Android 6.7.2 微信会出现这样的问题,考虑到兼容性,需要调用onMenuShareTimelineonMenuShareAppMessage接口向下兼容。

摇杆移动效果的实现

一开始尝试手写一个 JS 配合 CSS 去实现以上效果,但是结果都不是太完美,两端的 Touch 触摸事件也有兼容问题。后来找到一个 JS 库:nippleJS查看文档后发现刚好能实现想要的效果(上图就是通过nippleJS实现的)。

音频自动播放在移动端失效

其实这个不能算个难题,只是在这次项目也遇到了,记录下来。

原因就是系统会自动忽视 autoplay 属性(据说是考虑到流量消耗以及用户体验的原因,iOS 和 Android 都存在)。

解决方法:

1
2
3
4
//监听WeixinJSBridgeReady事件,然后再通过.play()函数触发播放。
document.addEventListener('WeixinJSBridgeReady', function() {
document.getElementById('audios').play();
});