微信小程序内嵌H5页面生成分享图片并下载的坑

1.需求

最近传化产品大佬又在搞事情,
在微信小程序内嵌H5的页面上实现分享搜索结果的功能,将车辆定位的搜索结果页生成图片并附带上二维码分享出去。我一听,这好说,不就是分享图片保存图片的功能么。

整理一下需求:
点击分享定位按钮,弹出层,将当前搜索结果页面截图

点击保存按钮,将图片保存到本地

2.方案

根据需求,定了我的基本方案
html -> canvas -> image -> a[download]

  • html2canvas.js:可将 htmldom 转为 canvas 元素。传送门
  • canvasAPI:toDataUrl() 可将 canvas 转为 base64 格式
  • 创建 a[download] 标签触发 click 事件实现下载

3.采坑表演

首先我们用到了html2canvas.js
根据官网介绍是通过js将DOM节点遍历一遍,然后基于DOM元素和样式来绘制canvas。有些样式可能无法理解,所以最后出来的结果有可能不大一样。

1
2
3
4
5
6
7
8
9
// v0.5.0
html2canvas(element, options).then(canvas => {
// 现在你已经拿到了canvas DOM元素
});

// v1.0.0
html2canvas(element, {...options}).then(canvas => {
// 现在你已经拿到了canvas DOM元素
});

基本流程大概是:

  1. 递归遍历每个节点。
  2. 考虑节点的层级问题。
  3. 从底层开始画到canvas上。(跟浏览器本身的渲染很像)

原先存在图片模糊的问题,在官方1.0版本上已经修复。
先获取设备像素比,并根据比例创建尺寸更大的 canvas。如二倍屏就是二倍

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
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* 根据window.devicePixelRatio获取像素比
*/
function DPR() {
if (window.devicePixelRatio && window.devicePixelRatio > 1) {
return window.devicePixelRatio;
}
return 1;
}

/**
* 绘制canvas
*/
async function drawCanvas(selector) {
// 获取想要转换的 DOM 节点
const dom = document.querySelector(selector);
const box = window.getComputedStyle(dom);
// DOM 节点计算后宽高
const width = parseValue(box.width);
const height = parseValue(box.height);
// 获取像素比
const scaleBy = DPR();
// 创建自定义 canvas 元素
const canvas = document.createElement('canvas');

// 设定 canvas 元素属性宽高为 DOM 节点宽高 * 像素比
canvas.width = width * scaleBy;
canvas.height = height * scaleBy;
// 设定 canvas css宽高为 DOM 节点宽高
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
// 获取画笔
const context = canvas.getContext('2d');

// 将所有绘制内容放大像素比倍
context.scale(scaleBy, scaleBy);

// 将自定义 canvas 作为配置项传入,开始绘制
return await html2canvas(dom, {canvas});
}

3.1生成的截图里面,地图显示不出来

排查后发现是地图的图片跨域了,因为我们地图用的是百度地图,所以肯定跨域了。

canvas是可以绘制跨域的图片,但是此时的canvas是受到污染的,污染状态下的canvas是无法通过toDataUrl()来生成图片的。

解决方案:

方案 | 尝试 | 结局 | 分析原因
—|—|—|—|—
方案一 | 一般出现跨域, 只需要在图片设置这个属性: crossorigin=”anonymous”允许跨域即可 | 惨败 | 你设置crossOrigin=”anonymous”是表明你想跨域获取这张图片,好用在canvas.toDataURL()上,但是服务端不一定同意啊,服务端添加了access…这个字段并且value是*或者你网站的域名才行,否则就认为你无权用,结果就是无法显示
方案二 | 通过传入html2canvas的配置项中增加{ useCORS: true, useTainted: false } | 惨败 | 看了下html2canvas源码,也是通过设置crossOrigin=”anonymous”来实现图片跨域,失败原因同上
方案三 | 前端写一个node中间层来进行服务器转发 | 暂未尝试 | 考虑百度地图的图片是实时变化加载的,获取具体跨域图片的url地址难度较大
最终方案 | 不用百度地图作为背景,改为用一张静态图片 | 勉强交付

3.2保存图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 在本地进行文件保存
* @param {String} data 要保存到本地的图片数据
* @param {String} filename 文件名
*/
saveFile(data, filename) {
const save_link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
save_link.href = data;
save_link.download = filename;

const event = document.createEvent('MouseEvents');
event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
save_link.dispatchEvent(event);
}

然而微信点击之后,没反应…
然后就去翻微信的jssdk

  1. 下载图片接口downloadImage,仅支持由uploadImage接口上传的图片
  2. uploadImage接口,仅支持由chooseImage接口选择相册里的图片
  3. chooseImage接口,是从本地相册选择图片
  4. 那么问题来了,图片都在相册了还需要我们干啥?

4.交付

最终的妥协方案:

  • 用户搜索车辆定位,查询出结果
  • 点击分享按钮,页面出现一个弹出框,弹出框背景为一张本地静态地图的图片,弹出框上加上二维码以及搜索结果
  • 通过html2canvas,将当前弹框部分生成一张base64图片
  • 微信中用户可长按页面上的图片调起 actionSheet 识别或保存图片

5.总结

因为第一次尝试在微信小程序内嵌H5的项目里做分享功能,所以需求阶段也不知道这个方案可不可行。一旦涉及到微信,就有可能出现一些之前考虑不到的问题和限制,所以,不管是产品经理还是程序员都要尽可能地多多了解。知道在微信中,能干什么,不能干什么,降低开发和反复沟通的成本。

希望以上内容能够对大家以后的开发有所帮助。


微信小程序内嵌H5页面生成分享图片并下载的坑
https://thaneyang.github.io/2019/03/微信小程序内嵌H5页面生成分享图片并下载的坑.html
作者
ThaneYang
发布于
2019年3月12日
许可协议