一矗蕊、需求
最近做了一個(gè)H5頁(yè)面短蜕,大概內(nèi)容是:用戶進(jìn)入H5頁(yè)面后做一些測(cè)試題,答完測(cè)試題后生成一個(gè)結(jié)果傻咖,將這些結(jié)果生成一張圖片忿危,用戶可以保存圖片到本地或者分享出去。
其實(shí)關(guān)鍵點(diǎn)就是在于怎么將Html生成一張圖片没龙,至于圖片的保存分享铺厨,微信是自帶這個(gè)功能的,長(zhǎng)按圖片即可彈出actionsheet來操作硬纤。
以下是最終完成的頁(yè)面截圖解滓,從左往右依次是:html中的展示、長(zhǎng)按html筝家、保存后的圖片洼裤。
二、功能
由于隱私問題溪王,不能提供上面的詳細(xì)代碼腮鞍,所以下面只做了一個(gè)關(guān)于生成圖片的Demo:點(diǎn)擊“生成圖片”按鈕后,將該按鈕隱藏掉莹菱,同時(shí)可長(zhǎng)按頁(yè)面來保存圖片或者分享等操作移国。
以下是最終完成的頁(yè)面截圖,從左到右依次是:html頁(yè)面道伟、點(diǎn)擊生成圖片后的html頁(yè)面迹缀、保存的圖片使碾。
三、 代碼實(shí)現(xiàn)
1. 方案與思路
- 通過
html2canvas.js
祝懂,將Html DOM節(jié)點(diǎn)轉(zhuǎn)換為canvas票摇,Html2Canvas官網(wǎng) ; - 通過
CanvasAPI
的toDataURL
砚蓬,將canvas
轉(zhuǎn)換為Base64
的格式矢门,并將它設(shè)置為img src
屬性值 - 在微信瀏覽器中,長(zhǎng)按
img
灰蛙,會(huì)彈起actionsheet
颅和,可以進(jìn)行保存、發(fā)送缕允、識(shí)別二維碼等操作峡扩。(注意:不要給圖片設(shè)置pointer-events: none
屬性,一旦給某個(gè)元素設(shè)置了這個(gè)屬性障本,如a
標(biāo)簽教届、img
標(biāo)簽,則無法跳轉(zhuǎn)或點(diǎn)擊)
2. 問題及解決辦法
1. 圖片模糊
在手機(jī)上保存圖片后看到的圖片比較模糊驾霜,這個(gè)是因?yàn)橐苿?dòng)端像素密度計(jì)算導(dǎo)致的案训。
設(shè)備像素比dpr(devicePixelRatio)是設(shè)備的物理像素分辨率與CSS像素分辨率的比值,該值也可以被解釋為像素大小的比例:即一個(gè)CSS像素的大小相對(duì)于一個(gè)物理像素的大小的比值粪糙。
MDN web docs 關(guān)于Window.devicePixelRatio的介紹强霎。
可以通過 window.devicePixelRatio
來獲取或者重置設(shè)備像素比。
所以可以通過將所有繪制內(nèi)容擴(kuò)大到像素比倍來使得圖片清晰蓉冈。
// 獲取設(shè)備的Dpr值
getDpr: function() {
if (window.devicePixelRatio && window.devicePixelRatio > 1) {
return window.devicePixelRatio;
}
return 1;
}
2.使用第三方圖片時(shí)會(huì)報(bào)錯(cuò)
當(dāng)直接通過給img src
賦值為第三方圖片時(shí)城舞,html的渲染及canvas的繪制都是沒有問題的,但是生成圖片時(shí)(使用 canvas.toDataURL
)寞酿,會(huì)報(bào)錯(cuò)(對(duì)于本地的圖片是沒有這個(gè)問題的):
翻譯一下就是:不能執(zhí)行canvas
元素的toDataURL API
家夺,因?yàn)楸晃廴镜漠嫴疾荒鼙惠敵觥?br>
究其原因是因?yàn)閏anvas中的圖片跨域了。看解釋
所以可以通過以下方法解決這個(gè)問題:
- 給
img
設(shè)置crossOrigin
屬性為Anonymous
伐弹; - 圖片的服務(wù)端允許跨域(像一些存放圖片元素的服務(wù)器拉馋,后臺(tái)應(yīng)該是可以配置的,本例中的頭像使用的是本人目前的微信頭像惨好,頁(yè)面的二維碼是本地的圖片)
code演示:將需要渲染的第三方圖片轉(zhuǎn)為Base64的格式并設(shè)置crossOrigin
屬性煌茴,賦值給需要展示的img src
// 將圖片轉(zhuǎn)為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'));
};
// 結(jié)合合適的CORS響應(yīng)頭日川,實(shí)現(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. 生成的圖片中要排除一些元素
在整個(gè)頁(yè)面中蔓腐,我們只需要將部分元素生成圖片,將其他元素排除逗鸣『献。可以有兩個(gè)方案:
A. 將你需要生成圖片的元素放到一個(gè)容器中绰精,可以將這個(gè)容器作為dom
的傳參撒璧,不需要生成在圖片上的元素不要放到這個(gè)容器中
html2canvas(dom, {}).then(function(canvas) {});
B. 通過設(shè)置html
元素的data-html2canvas-ignore
屬性透葛,將該元素排除
<div class="generatePicture" data-html2canvas-ignore @click="generateImage" v-show="afterCanvasImageHide">
<p class="optText">生成圖片</p>
</div>
4. 生成圖片
這里有兩個(gè)點(diǎn):
生成圖片后,Html中二維碼的下面看到的顯示是:A:“長(zhǎng)按保存圖片”卿樱,但是分享出去的圖片上面顯示的是另外一個(gè)文字描述B(這是常見的需求)僚害;
思路:生成圖片之前,將B文字隱藏opacity:0
繁调,當(dāng)要生成圖片的時(shí)候萨蚕,再將B文字顯示opacity:1
,生成圖片完成之后蹄胰,再將B文字隱藏opacity:0
岳遥。(在文字交換的時(shí)候會(huì)出現(xiàn)閃爍的問題,可以通過在生成圖片的時(shí)候加一個(gè)進(jìn)度條來掩蓋這種問題)生成圖片之后裕寨,用戶看到的是html的內(nèi)容浩蓉,但是長(zhǎng)按的時(shí)候其實(shí)是在圖片上操作。
思路:生成圖片之后宾袜,將生成的圖片展示在最上層捻艳,并設(shè)置opacity:0
,這樣用戶長(zhǎng)按的就是這張透明度為0的圖片了庆猫。
generateImage: function() {
var _this = this;
var scanTextElem = document.getElementById('scanText');
scanTextElem.style.opacity = '1';
// 獲取想要轉(zhuǎn)換的dom節(jié)點(diǎn)
var dom = document.getElementById('app');
var box = window.getComputedStyle(dom);
// dom節(jié)點(diǎn)計(jì)算后寬高
var width = _this.parseValue(box.width);
var height = _this.parseValue(box.height);
// 獲取像素比
var scaleBy = _this.getDpr();
// 創(chuàng)建自定義的canvas元素
var canvas = document.createElement('canvas');
// 設(shè)置canvas元素屬性寬高為 DOM 節(jié)點(diǎn)寬高 * 像素比
canvas.width = width * scaleBy;
canvas.height = height * scaleBy;
// 設(shè)置canvas css 寬高為DOM節(jié)點(diǎn)寬高
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
// 獲取畫筆
var context = canvas.getContext('2d');
// 將所有繪制內(nèi)容放大像素比倍
context.scale(scaleBy, scaleBy);
// 設(shè)置需要生成的圖片的大小认轨,不限于可視區(qū)域(即可保存長(zhǎng)圖)
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轉(zhuǎn)換成圖片渲染到頁(yè)面上
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);
});
}
注意:
在實(shí)際項(xiàng)目中,有的可能是一屏展示的效果圖月培,有的會(huì)要求生成長(zhǎng)圖嘁字。
這個(gè)是可以通過html2canvas
的傳參width、height
解決的杉畜。更多傳參選項(xiàng)可參考Html2canvas configuration options拳锚。
一屏展示的,可以設(shè)置寬高為整個(gè) windows
的寬高寻行,也就是可視區(qū)域的寬高
html2canvas(dom,{
width: window.innerWidth,
height: window.innerHeight,
}).then(canvas => {
document.body.appendChild(canvas)
});
對(duì)于要求生成長(zhǎng)圖的霍掺,將width、height
分別設(shè)置為拌蜘,需要生成長(zhǎng)圖的容器的寬高杆烁,例如本例中的容器#app
的寬高
html2canvas(dom,{
width: document.getElementById('app').style.width,
height: document.getElementById('app').style.height
}).then(canvas => {
document.body.appendChild(canvas)
});
最后附上我的源碼地址:myHtml2canvasDemo
歡迎交流學(xué)習(xí)简卧。
如果這篇文章有幫到你兔魂,記得點(diǎn)個(gè)贊哦!