一, 长按图片为 base64 的图片, 识别图中的二维码时, base64 的字符个数有限制
经过测试大于大概为 258286 字符 (大约 500KB) 的时候, 在识别图时可以弹出正常的菜单, 但是点击菜单项 "识别图中二维码" 则无反应.
需求背景: 拓展业务时, 都会生成一个二维码页面用于分享给别人; 而二维码页面包含有, 二维码图片, 公司 logo 图, 背景图, 动态产品介绍文案 等构成的一个页面.
用于分享出去的东西最好的预期是, 能把整个页面转换为图片分享出去效果是最好的, 而不单单只分享一个二维码图片. 所以就有以下需求点需要解决:
把页面转换为图片
让微信能正常识别图中的二维码
利用微信内置的长按功能即可以实现以上需求. 在长按后, 弹出来的菜单中, 可以自己选择 "保存为图片", 或者点击 "识别图中二维码" 直接进入推广页, 或者也可以直接发送给朋友.
根据以上需求点, 那么问题来了.
页面如何转换为图片呢?
通过 html2canvas 把页面转换为 canvas, 然后在通过 canvas.toDataURL 方法转换为 base64 图即可
用到的技术为 https://github.com/niklasvh/html2canvas
如何使用呢?
直接在自己项目中安装
yarn add html2canvas -D
在项目中引用
- import html2canvas from 'html2canvas';
- // 把页面生成为 base64 图
- html2canvas(document.body).then((canvas) => {
- const base64Image = canvas.toDataURL('image/png');
- console.log(` 转换出的 base64 图为:${base64Image}`);
- });
把转换出来的 base64 图, 替换掉页面即可, 用户是无感知的.
那么问题来了, 通过 canvas.toDataURL 转换出来的 base64 字符过长, 超出了微信 base64 字符限制, 这怎么办呢?
可以利用 canvas.toDataURL 转换为 image/jpeg 然后添加第二个参数, 改变图片质量, 把字符将到限制以下即可.
canvas.toDataURL('image/jpeg', 0.3) // 在指定图片格式为 image/jpeg 或 image/webp 的情况下, 可以从 0 到 1 的区间内选择图片的质量
更多详细的 toDataURL 方法的使用可以参考 MDN 文档 https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/toDataURL
二, 页面首次登录发起的微信静默登录后, 后台重定向回到当前页面, 要点击两次返回才能回到上一个页面问题.
要分析整个问题, 得先理解微信的静默登录的流程是如何的, 流程如下:
微信静默登录的地址为:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=[企业号 appId]&redirect_uri=[后台回调 url]&response_type=code&scope=snsapi_base&state=[前端页面 url]#wechat_redirect https://open.weixin.qq.com/connect/oauth2/authorize?appid=[企业号appId]&redirect_uri=[后台回调url]&response_type=code&scope=snsapi_base&state=[前端页面url]#wechat_redirect
第一 , 当页面请求某个后台接口时, 后台接口会返回一些东西, 让你知道用户未登录; 通常的做法是, 后台会返回状态码 401, 然后前端根据 401 状态吗, 执行 location.replace([微信静默登录地址]), 让页面加载这连接就会执行静默登录.
第二, 加载以上连接后, 微信会把响应的一些参数 code 和参数 state 指定的前端页面拼接到传入的后台回调 url, 然后执行这个后台 url 请求.
第三, 请求后台时, 后台会根据拼接传给他的参数, 去获取用户的 openid, 和 access_token, 成功后, 会重定向回到 state 参数指定的前端页面 url.
原因分析:
后台重定向是无法替换掉历史记录的, 所以导致这个问题.
对于这个问题的误解: 当发起微信静默登录时, 采用 location.replace()时, 一直以为, 为什么不会替换掉历史记录, 问题的考虑点就错了, 问题点不在这, 因为问题点是重定向不会替换掉历史记录. 所以当用户登录成功后, 点击第一次返回, 会回到当前页, 再次点击返回时, 才能回到上一个页面, 所以会出现, 点击 2 次返回才能回到上一个页面问题.
那么问题来了, 如何解决这个问题呢?
假如有两个页面 A(不需要登录态),B(需要登录态)
首先得分析一下页面的历史记录如下:
当进入页面 A 时, 历史记录为 1, 当点击跳转到 B 页面时, 执行了微信静默登录, 此时历史记录 + 1 即为 2.
然后登录成功后重定向回到 B 页面, 此时历史记录再 + 1 即为 3 了.
我们可以重定向回到 B 页面时, 还未进入到 B 页面, 就让历史回退 history.go(-2)到 A 页面(此时历史记录为 1), 然后 A 页面在根据一些参数来判断, 跳转到 B 页面(此时的历史记录为 2), 即可实现当点击 B 页面可以直接回到 A 页面.
ps: 这里我的项目主要用到了中间页进行登录了. 没有中间页的情况, 直接 history.go(-1)即可.
还是直接上代码吧, 代码 (截取的是自己 vue 项目的一个实现, 部分代码) 如下:
vue 项目为单页面应用, 所以把逻辑放到全局路由守卫(当进入 A,B 页面时都会执行, 即进入 A 或 B 路由时, 首先会执行的逻辑判断
- // 静默登录时, 传给 state 的前端 url 参数, 后边加了一个参数 "?isLogin=true" 添加了这个参数, 同时也解决了另一个静默登录遇到的坑, 后续会讲到.
- // const stateUrl = encodeURIComponent('http://www.demo1.com/?isLogin=true#/pageA');
- // 连接如: `https://open.weixin.qq.com/connect/oauth2/authorize?appid=[企业号 appId]&redirect_uri=[后台回调 url]&response_type=code&scope=snsapi_base&state=${stateUrl}#wechat_redirect`
- Vue.beforeEach((to, from, next) => {
- if (!window.localStorage) {
- next();
- }
- const prevUrl = localStorage.getItem('prevUrl');
- if (prevUrl) { // 假如存在这个参数, 说明是从静默登录成功后的页面跳回来的. 即这里, 判断为是从 B 页面跳回来的.
- localStorage.removeItem('prevUrl');
- next({ // 跳回静默登录成功后的页面, 这里指的即是跳回到 B 页面.
- path: `${prevUrl.split('#')[1]}`,
- query: {
- cache: false
- }
- });
- }
- if (/\?isLogin=true/g.test(location.href)) {
- localStorage.setItem('prevUrl', `${location.hash.split('?')[0]}`); // 添加这 prevUrl 参数用于判断是否存在是从 B 跳到 A 的, 相当于保存了 B 页面的连接, 即静默登录成功后进入的页面连接.
- history.go(-2); // 回退到 A 页面
- }
- next();
- })
三, iphone6 和 iphone6s 中发现, 静默登录后页面不刷新, 或者白屏
假如进入 A 页面时发起静默登录, 成功登录后回调到 A 页面, 要是 A 页面连接不变, A 页面就不会刷新, 导致, 假如刚进入 A 页面, 还未渲染 DOM 时, 就发起静默的登录, 重定向回来时, 看起来就是白屏效果, 或者假如你传给 state 的参数大于 128 字节时(即字符长度为 64). 超过的话, 会被截取掉.
想要预防 state 大于限制被截取掉, 可以把前端的回调地址不要放在 state 参数里, 而是自己取一个参数拼接放在后端的回调参数 redirect_uri 里即可, 然后在跟后端约定就好, 拼接如下:
- const feUrl = 'http://www.demo1.com/?isLogin=true#/pageA'; // 前端地址
- const authCallbackUrl = `${apiSourceDomain}/wechat/zlAuthCallback.jhtml?redirectUrl=${encodeURIComponent(state)}`; // 后端地址
- location.replace(`https://open.weixin.qq.com/connect/oauth2/authorize?appid=[企业号 appId]&redirect_uri=${authCallbackUrl}&response_type=code&scope=snsapi_base&state=${stateUrl}#wechat_redirect`); // 发起微信静默登录
即静默登录成功后, 后台到时候想要 302 重定向回来前端页面, 取的参数值应该为上边的 redirectUrl 参数了, 而不是微信拼接的 state 参数了, 这个跟后台约定好即可.
解决方案:
发起静默登录时, 在参数 state 指定的前端回调页面连接里, 加入任意参数即可. 添加如下:
- const feUrl = encodeURIComponent('http://www.demo1.com/?isLogin=true#/pageA');
- location.replace(`https://open.weixin.qq.com/connect/oauth2/authorize?appid=[企业号 appId]&redirect_uri=[后台回调 url]&response_type=code&scope=snsapi_base&state=${feUrl}#wechat_redirect`);
四, ios 左右边缘滑动返回前进页面动画与 h5 用户自定义页面切换动画冲突问题
导致滑动切换页面时执行了 ios 的动画之后又执行自定义动画. 看起来就是闪了一下自定义动画.
解决方案:
监听左右滑动手势和 touchstart 事件清除掉自定义动画即可.
同时加入滑动后多少 ms 后恢复动画, 不然滑动后就真的没有自定义动画了 , 已防止用户不是边缘滑动时清除掉的自定义动画恢复.
多少 ms 后恢复 建议设置为切换动画执行时间的两倍即可.
上边为什么要监听 touchstart 事件呢, 因为经过测试发现滑动边缘返回或前进时, 不一定执行滑动事件, 因为封装的滑动事件是有最小距离的, 超过才执行的.
然后又为了防止用户触摸屏幕时会执行 touchstart 事件从而导致切换动画清除掉, 在 click 事件发生时要回复动画.
截取 vue 项目中的部分代码如下:
- // <transition :name="transitionName">
- // <router-view @click.native="onClick" v-swipe="{fn:onSwipe, trigger:'touchstart'}"></router-view>
- //</transition>
- export default {
- data: {
- return {
- routeMap: {
- count: 0,
- '/': 0,
- },
- transitionName: '',
- touchHandler: null,
- }
- }
- watch: {
- $route(to, from) {
- // 切换路由, 判断是前进页面, 或后退页面, 相应改变 this.transitionName = 'next';
- // 或 this.transitionName = 'prev';
- const toIndex = this.routeMap[to.path];
- const fromIndex = this.routeMap[from.path];
- if (toIndex !== undefined) {
- if (!fromIndex || parseInt(toIndex, 10)> parseInt(fromIndex, 10)) {
- this.transitionName = 'next';
- } else {
- this.transitionName = 'prev';
- }
- } else {
- this.routeMap.count += 2;
- this.routeMap[to.path] = this.routeMap.count;
- // 第一个页面不需要动画, 因为每个子路由的 from 都是根路由(即使你第一个访问的是子路由)
- if (from.path !== '/') {
- this.transitionName = 'next';
- }
- }
- // 清除掉自定义动画
- this.touchHandler && this.touchHandler();
- }
- }
- methods: {
- ...
- // 监听点击事件
- onClick() {
- this.touchHandler = null; // 恢复自定义动画
- },
- // 监听向左向右滑动, 和 touchstart 事件
- onSwipe() { // 解决 ios 系统版本在 11.0 以上, 滑动会执行原生动画和 vue 自定义动画冲突问题
- if (Device.isIOS() && (Device.iosVersionCompare('11.0') !== -1)) { // 大于 11.0
- this.touchHandler = () => {
- this.transitionName = ''; // 清除掉自定义动画
- };
- this.Timer && clearTimeout(this.Timer);
- this.Timer = setTimeout(() => { // 恢复自定义动画
- this.touchHandler = null;
- }, 2000);
- }
- },
- ...
- }
- }
ps: 这个不是微信里的坑, 但是也一起记录了, 触摸事件可以自己添加, vue-touch 触摸插件, 上边的 v-swipe 指令我是自己在项目里通过 Vue.derective('swipe',{...}) 这种方式自己添加的.
参考
- HTMLCanvasElement.toDataURL() https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/toDataURL
- https://github.com/niklasvh/html2canvas
微信网页授权
来源: http://www.qdfuns.com/article/13989/8fed58c72fd4be96e5fa3a9429ccea2a.html