一、需求
最近做了一个H5页面,大概内容是:用户进入H5页面后做一些测试题,答完测试题后生成一个结果,将这些结果生成一张图片,用户可以保存图片到本地或者分享出去。
其实关键点就是在于怎么将Html生成一张图片,至于图片的保存分享,微信是自带这个功能的,长按图片即可弹出actionsheet来操作。
以下是最终完成的页面截图,从左往右依次是:html中的展示、长按html、保存后的图片。
二、功能
由于隐私问题,不能提供上面的详细代码,所以下面只做了一个关于生成图片的Demo:点击“生成图片”按钮后,将该按钮隐藏掉,同时可长按页面来保存图片或者分享等操作。
以下是最终完成的页面截图,从左到右依次是:html页面、点击生成图片后的html页面、保存的图片。
三、 代码实现
1. 方案与思路
- 通过
html2canvas.js
,将Html DOM节点转换为canvas,Html2Canvas官网 ; - 通过
CanvasAPI
的toDataURL
,将canvas
转换为Base64
的格式,并将它设置为img src
属性值 - 在微信浏览器中,长按
img
,会弹起actionsheet
,可以进行保存、发送、识别二维码等操作。(注意:不要给图片设置pointer-events: none
属性,一旦给某个元素设置了这个属性,如a
标签、img
标签,则无法跳转或点击)
2. 问题及解决办法
1. 图片模糊
在手机上保存图片后看到的图片比较模糊,这个是因为移动端像素密度计算导致的。
设备像素比dpr(devicePixelRatio)是设备的物理像素分辨率与CSS像素分辨率的比值,该值也可以被解释为像素大小的比例:即一个CSS像素的大小相对于一个物理像素的大小的比值。
MDN web docs 关于Window.devicePixelRatio的介绍。
可以通过 window.devicePixelRatio
来获取或者重置设备像素比。
所以可以通过将所有绘制内容扩大到像素比倍来使得图片清晰。
// 获取设备的Dpr值
getDpr: function() {
if (window.devicePixelRatio && window.devicePixelRatio > 1) {
return window.devicePixelRatio;
}
return 1;
}
2.使用第三方图片时会报错
当直接通过给img src
赋值为第三方图片时,html的渲染及canvas的绘制都是没有问题的,但是生成图片时(使用 canvas.toDataURL
),会报错(对于本地的图片是没有这个问题的):
翻译一下就是:不能执行canvas
元素的toDataURL API
,因为被污染的画布不能被输出。
究其原因是因为canvas中的图片跨域了。看解释
所以可以通过以下方法解决这个问题:
- 给
img
设置crossOrigin
属性为Anonymous
; - 图片的服务端允许跨域(像一些存放图片元素的服务器,后台应该是可以配置的,本例中的头像使用的是本人目前的微信头像,页面的二维码是本地的图片)
code演示:将需要渲染的第三方图片转为Base64的格式并设置crossOrigin
属性,赋值给需要展示的img src
// 将图片转为base64格式
img2base64: function(url, crossOrigin) {
// 这里使用了 ES6 的Promise,及箭头函数
return new Promise(resolve => {
const img = new Image();
img.onload = () => {
const c = document.createElement('canvas');
c.width = img.naturalWidth;
c.height = img.naturalHeight;
const cxt = c.getContext('2d');
cxt.drawImage(img, 0, 0);
// 得到图片的base64编码数据
resolve(c.toDataURL('image/png'));
};
// 结合合适的CORS响应头,实现在画布中使用跨域<img>元素的图像
crossOrigin && img.setAttribute('crossOrigin', crossOrigin);
img.src = url;
});
},
// 使用
var imgUrl1 = 'http://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTKVkoe7Viae4lreoZBybEywysxHlnlqplGTbaJLQI7pV8W5KMFK1DqBrNntO5O9wT0YYP9cgP6m4dA/132';
_this.img2base64(imgUrl1, 'Anonymous').then(function(res) {
_this.avatar = res;
});
3. 生成的图片中要排除一些元素
在整个页面中,我们只需要将部分元素生成图片,将其他元素排除。可以有两个方案:
A. 将你需要生成图片的元素放到一个容器中,可以将这个容器作为dom
的传参,不需要生成在图片上的元素不要放到这个容器中
html2canvas(dom, {}).then(function(canvas) {});
B. 通过设置html
元素的data-html2canvas-ignore
属性,将该元素排除
<div class="generatePicture" data-html2canvas-ignore @click="generateImage" v-show="afterCanvasImageHide">
<p class="optText">生成图片</p>
</div>
4. 生成图片
这里有两个点:
生成图片后,Html中二维码的下面看到的显示是:A:“长按保存图片”,但是分享出去的图片上面显示的是另外一个文字描述B(这是常见的需求);
思路:生成图片之前,将B文字隐藏opacity:0
,当要生成图片的时候,再将B文字显示opacity:1
,生成图片完成之后,再将B文字隐藏opacity:0
。(在文字交换的时候会出现闪烁的问题,可以通过在生成图片的时候加一个进度条来掩盖这种问题)生成图片之后,用户看到的是html的内容,但是长按的时候其实是在图片上操作。
思路:生成图片之后,将生成的图片展示在最上层,并设置opacity:0
,这样用户长按的就是这张透明度为0的图片了。
generateImage: function() {
var _this = this;
var scanTextElem = document.getElementById('scanText');
scanTextElem.style.opacity = '1';
// 获取想要转换的dom节点
var dom = document.getElementById('app');
var box = window.getComputedStyle(dom);
// dom节点计算后宽高
var width = _this.parseValue(box.width);
var height = _this.parseValue(box.height);
// 获取像素比
var scaleBy = _this.getDpr();
// 创建自定义的canvas元素
var 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';
// 获取画笔
var context = canvas.getContext('2d');
// 将所有绘制内容放大像素比倍
context.scale(scaleBy, scaleBy);
// 设置需要生成的图片的大小,不限于可视区域(即可保存长图)
var w = document.getElementById('app').style.width;
var h = document.getElementById('app').style.height;
html2canvas(dom, {
allowTaint: true,
width: w,
height: h,
useCORS: true
}).then(function(canvas) {
// 将canvas转换成图片渲染到页面上
var url = canvas.toDataURL('image/png');// base64数据
var image = new Image();
image.src = url;
document.getElementById('shareImg').appendChild(image);
_this.afterCanvasImageHide = false;
scanTextElem.style.opacity = '0';
_this.showToast = true;
setTimeout(function() {
_this.showToast = false;
}, 1000);
});
}
注意:
在实际项目中,有的可能是一屏展示的效果图,有的会要求生成长图。
这个是可以通过html2canvas
的传参width、height
解决的。更多传参选项可参考Html2canvas configuration options。
一屏展示的,可以设置宽高为整个 windows
的宽高,也就是可视区域的宽高
html2canvas(dom,{
width: window.innerWidth,
height: window.innerHeight,
}).then(canvas => {
document.body.appendChild(canvas)
});
对于要求生成长图的,将width、height
分别设置为,需要生成长图的容器的宽高,例如本例中的容器#app
的宽高
html2canvas(dom,{
width: document.getElementById('app').style.width,
height: document.getElementById('app').style.height
}).then(canvas => {
document.body.appendChild(canvas)
});
最后附上我的源码地址:myHtml2canvasDemo
欢迎交流学习。
如果这篇文章有帮到你,记得点个赞哦!