一, 需求
最近做了一个 H5 页面, 大概内容是: 用户进入 H5 页面后做一些测试题, 答完测试题后生成一个结果, 将这些结果生成一张图片, 用户可以保存图片到本地或者分享出去.
其实关键点就是在于怎么将 html 生成一张图片, 至于图片的保存分享, 微信是自带这个功能的, 长按图片即可弹出 actionsheet 来操作.
以下是最终完成的页面截图, 从左往右依次是: HTML 中的展示, 长按 HTML, 保存后的图片.
nhj.PNG
二, 功能
由于隐私问题, 不能提供上面的详细代码, 所以下面只做了一个关于生成图片的 Demo: 点击 "生成图片" 按钮后, 将该按钮隐藏掉, 同时可长按页面来保存图片或者分享等操作.
以下是最终完成的页面截图, 从左到右依次是: HTML 页面, 点击生成图片后的 HTML 页面, 保存的图片.
html2canvas-demo2.PNG
三, 代码实现
1. 方案与思路
通过 html2canvas.JS, 将 HTML DOM 节点转换为 canvas,Html2Canvas 官网 https://html2canvas.hertzen.com/ ;
通过 CanvasAPI 的 toDataURL, 将 canvas 转换为 Base64 的格式, 并将它设置为 img src 属性值
在微信浏览器中, 长按 img, 会弹起 actionsheet, 可以进行保存, 发送, 识别二维码等操作.(注意: 不要给图片设置
pointer-events: none
属性, 一旦给某个元素设置了这个属性, 如 a 标签, img 标签, 则无法跳转或点击)
2. 问题及解决办法
1. 图片模糊
在手机上保存图片后看到的图片比较模糊, 这个是因为移动端像素密度计算导致的.
设备像素比 dpr(devicePixelRatio) 是设备的物理像素分辨率与 CSS 像素分辨率的比值, 该值也可以被解释为像素大小的比例: 即一个 CSS 像素的大小相对于一个物理像素的大小的比值.
MDN web docs 关于 Windows.devicePixelRatio 的介绍.
可以通过 Windows.devicePixelRatio 来获取或者重置设备像素比.
dpr.PNG
所以可以通过将所有绘制内容扩大到像素比倍来使得图片清晰.
- // 获取设备的 Dpr 值
- getDpr: function() {
- if (Windows.devicePixelRatio && Windows.devicePixelRatio> 1) {
- return Windows.devicePixelRatio;
- }
- return 1;
- }
2. 使用第三方图片时会报错
当直接通过给 img src 赋值为第三方图片时, HTML 的渲染及 canvas 的绘制都是没有问题的, 但是生成图片时 (使用 canvas.toDataURL), 会报错 (对于本地的图片是没有这个问题的):
toDataUrl_bug.PNG
翻译一下就是: 不能执行 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 = Windows.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 https://html2canvas.hertzen.com/configuration .
一屏展示的, 可以设置宽高为整个 Windows 的宽高, 也就是可视区域的宽高
- html2canvas(dom,{
- width: Windows.innerWidth,
- height: Windows.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)
- });
最后附上我的源码地址: https://github.com/lulingling/myHtml2canvasDemo
欢迎交流学习.
如果这篇文章有帮到你, 记得点个赞哦!
来源: http://www.jianshu.com/p/892e701592ee