一、需求
最近做了一個H5頁面,大概內容是:用戶進入H5頁面后做一些測試題,答完測試題后生成一個結果,將這些結果生成一張圖片,用戶可以保存圖片到本地或者分享出去。
其實關鍵點就是在于怎么將Html生成一張圖片,至于圖片的保存分享,微信是自帶這個功能的,長按圖片即可彈出actionsheet來操作。
以下是最終完成的頁面截圖,從左往右依次是:html中的展示、長按html、保存后的圖片。
二、功能
由于隱私問題,不能提供上面的詳細代碼,所以下面只做了一個關于生成圖片的Demo:點擊“生成圖片”按鈕后,將該按鈕隱藏掉,同時可長按頁面來保存圖片或者分享等操作。
以下是最終完成的頁面截圖,從左到右依次是:html頁面、點擊生成圖片后的html頁面、保存的圖片。
三、 代碼實現(xiàn)
1. 方案與思路
- 通過
html2canvas.js
,將Html DOM節(jié)點轉換為canvas,Html2Canvas官網(wǎng) ; - 通過
CanvasAPI
的toDataURL
,將canvas
轉換為Base64
的格式,并將它設置為img src
屬性值 - 在微信瀏覽器中,長按
img
,會彈起actionsheet
,可以進行保存、發(fā)送、識別二維碼等操作。(注意:不要給圖片設置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
),會報錯(對于本地的圖片是沒有這個問題的):
翻譯一下就是:不能執(zhí)行canvas
元素的toDataURL API
,因為被污染的畫布不能被輸出。
究其原因是因為canvas中的圖片跨域了。看解釋
所以可以通過以下方法解決這個問題:
- 給
img
設置crossOrigin
屬性為Anonymous
; - 圖片的服務端允許跨域(像一些存放圖片元素的服務器,后臺應該是可以配置的,本例中的頭像使用的是本人目前的微信頭像,頁面的二維碼是本地的圖片)
code演示:將需要渲染的第三方圖片轉為Base64的格式并設置crossOrigin
屬性,賦值給需要展示的img src
// 將圖片轉為base64格式
img2base64: function(url, crossOrigin) {
// 這里使用了 ES6 的Promise,及箭頭函數(shù)
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編碼數(shù)據(jù)
resolve(c.toDataURL('image/png'));
};
// 結合合適的CORS響應頭,實現(xiàn)在畫布中使用跨域<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
。(在文字交換的時候會出現(xiàn)閃爍的問題,可以通過在生成圖片的時候加一個進度條來掩蓋這種問題)生成圖片之后,用戶看到的是html的內容,但是長按的時候其實是在圖片上操作。
思路:生成圖片之后,將生成的圖片展示在最上層,并設置opacity:0
,這樣用戶長按的就是這張透明度為0的圖片了。
generateImage: function() {
var _this = this;
var scanTextElem = document.getElementById('scanText');
scanTextElem.style.opacity = '1';
// 獲取想要轉換的dom節(jié)點
var dom = document.getElementById('app');
var box = window.getComputedStyle(dom);
// dom節(jié)點計算后寬高
var width = _this.parseValue(box.width);
var height = _this.parseValue(box.height);
// 獲取像素比
var scaleBy = _this.getDpr();
// 創(chuàng)建自定義的canvas元素
var canvas = document.createElement('canvas');
// 設置canvas元素屬性寬高為 DOM 節(jié)點寬高 * 像素比
canvas.width = width * scaleBy;
canvas.height = height * scaleBy;
// 設置canvas css 寬高為DOM節(jié)點寬高
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
// 獲取畫筆
var context = canvas.getContext('2d');
// 將所有繪制內容放大像素比倍
context.scale(scaleBy, scaleBy);
// 設置需要生成的圖片的大小,不限于可視區(qū)域(即可保存長圖)
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數(shù)據(jù)
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
的寬高,也就是可視區(qū)域的寬高
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
歡迎交流學習。
如果這篇文章有幫到你,記得點個贊哦!