基于html2canvas實(shí)現(xiàn)網(wǎng)頁(yè)保存為圖片及圖片清晰度優(yōu)化

本次技術(shù)調(diào)研來(lái)源于H5項(xiàng)目中的一個(gè)重要功能需求:實(shí)現(xiàn)微信長(zhǎng)按網(wǎng)頁(yè)保存為截圖稿械。

這里有個(gè)栗子(請(qǐng)用微信打開(kāi)拙已,長(zhǎng)按圖片即可保存):3分鐘探索你的知識(shí)邊界

將整個(gè)網(wǎng)頁(yè)保存為圖片是一個(gè)十分有趣的功能,常見(jiàn)于H5活動(dòng)頁(yè)的結(jié)尾頁(yè)分享袜茧。以下則是項(xiàng)目中調(diào)研和踩坑的一些小結(jié)和匯總菜拓。


一、實(shí)現(xiàn)HTML頁(yè)面保存為圖片

1.1 已知可行方案

現(xiàn)有已知能夠?qū)崿F(xiàn)網(wǎng)頁(yè)保存為圖片的方案包括:

  • 方案1:將DOM改寫為canvas笛厦,然后利用canvas的toDataURL方法實(shí)現(xiàn)將DOM輸出為包含圖片展示的data URI
  • 方案2:使用html2canvas.js實(shí)現(xiàn)(可選搭配Canvas2Image.js實(shí)現(xiàn)網(wǎng)頁(yè)保存為圖片)
  • 方案3:使用rasterizeHTML.js實(shí)現(xiàn)

1.2 解決方案的選擇

  • 方案1:需要手動(dòng)計(jì)算每個(gè)DOM元素的Computed Style纳鼎,然后需要計(jì)算好元素在canvas的大小位置等屬性。

    方案1難點(diǎn)

    1. 相當(dāng)于完全重寫了整個(gè)頁(yè)面的布局樣式,增加了工作量贱鄙。
    2. 由于canvas中沒(méi)有的對(duì)象概念劝贸,對(duì)于元素豐富、布局復(fù)雜的頁(yè)面逗宁,不易重構(gòu)悬荣。
    3. 所有DOM元素改寫進(jìn)canvas會(huì)帶來(lái)一些困難,例如:難以支持響應(yīng)式疙剑,圖片元素清晰度不佳和文字點(diǎn)擊區(qū)域識(shí)別問(wèn)題等。
  • 方案2:該類功能中Github上stars最多(至今仍在維護(hù))践叠,Stack Overflow亦有豐富的討論言缤。只需簡(jiǎn)單調(diào)用html2canvas方法并設(shè)定配置項(xiàng)即可。

  • 方案3:該方案的限制較多禁灼,目前僅支持3類可轉(zhuǎn)為canvas的目標(biāo)格式: 頁(yè)面url管挟,html字符串和document對(duì)象。

小結(jié): html2canvas是目前實(shí)現(xiàn)網(wǎng)頁(yè)保存為圖片功能的綜合最佳選擇弄捕。

1.3 html2canvas的使用方法

官方GitHub:https://github.com/niklasvh/h...

以下描述針對(duì)html2canvas版本是0.5.0-beta4

1.3.1 實(shí)現(xiàn)保存為圖片的第一步:html轉(zhuǎn)為canvas

基于html2canvas.js可將一個(gè)元素渲染為canvas僻孝,只需要簡(jiǎn)單的調(diào)用html2canvas(element[, options]);即可。下列html2canvas方法會(huì)返回一個(gè)包含有<canvas>元素的promise

html2canvas(document.body).then(function(canvas) {
    document.body.appendChild(canvas);
});

1.3.2 實(shí)現(xiàn)保存為圖片的第二步:canvas轉(zhuǎn)image

上一步生成的canvas即為包含目標(biāo)元素的<canvas>元素對(duì)象守谓。實(shí)現(xiàn)保存圖片的目標(biāo)只需要將canvas轉(zhuǎn)image即可穿铆。

這里的轉(zhuǎn)換方案有2種

  • 方案1:基于原生canvas的toDataURL方法將canvas輸出為data: URI類型的圖片地址,再將該圖片地址賦值給<image>元素的src屬性即可
  • 方案2:使用第三方庫(kù)Canvas2Image.js斋荞,調(diào)用其convertToImage方法即可(GitHub

實(shí)際上荞雏,Canvas2Image.js也是基于canvas.toDataURL的封裝,相比原生的canvas API對(duì)于轉(zhuǎn)為圖片的功能上考慮更為具體(未壓縮的包大小為7.4KB)平酿,適合項(xiàng)目使用凤优。

二、生成圖片的清晰度優(yōu)化方案

2.1 基礎(chǔ)的清晰度優(yōu)化方案

最終圖片的清晰度取決于第一步中html轉(zhuǎn)換成的canvas的清晰度蜈彼。

現(xiàn)有解決方案參考筑辨;

基本原理為:
canvas的屬性widthheight屬性放大為2倍(或者設(shè)置為devicePixelRatio倍),最后將canvas的CSS樣式width和height設(shè)置為原先1倍的大小幸逆。

例如:希望在html中實(shí)際顯示的<canvas>寬高分別為160px,90px則可作如下設(shè)置

<canvas width="320" height="180" style="width:160px;height:90px;"></canvas>

參考上述文檔具體的使用案例如下棍辕;

convert2canvas() {

    var shareContent = YourTargetElem; 
    var width = shareContent.offsetWidth; 
    var height = shareContent.offsetHeight; 
    var canvas = document.createElement("canvas"); 
    var scale = 2; 

    canvas.width = width * scale; 
    canvas.height = height * scale; 
    canvas.getContext("2d").scale(scale, scale); 

    var opts = {
        scale: scale, 
        canvas: canvas, 
        logging: true, 
        width: width, 
        height: height 
    };
    html2canvas(shareContent, opts).then(function (canvas) {
        var context = canvas.getContext('2d');

        var img = Canvas2Image.convertToImage(canvas, canvas.width, canvas.height);

        document.body.appendChild(img);
        $(img).css({
            "width": canvas.width / 2 + "px",
            "height": canvas.height / 2 + "px",
        })
    });
}

2.2 進(jìn)階的清晰度優(yōu)化方案

上述設(shè)置可以解決通常情況下圖片不清晰的問(wèn)題,不過(guò)探索并沒(méi)有結(jié)束还绘。

實(shí)際在我們的項(xiàng)目中痢毒,即使作出2.1節(jié)的設(shè)置后,大果粒一般的渲染結(jié)果依然尷尬蚕甥。

下面直接給出3條進(jìn)一步的優(yōu)化策略:

  1. 更改百分比布局px布局(如果原先是百分比布局的話)
  2. 關(guān)閉canvas默認(rèn)的抗鋸齒設(shè)
  3. 設(shè)置模糊元素的widthheight為素材原有寬高哪替,然后通過(guò)transform: scale進(jìn)行縮放。這里scale的數(shù)值由具體需求決定菇怀。

基本原理

  1. 如果原來(lái)使用百分比設(shè)置元素寬高凭舶,請(qǐng)更改為px為單位的寬高晌块,避免樣式二次計(jì)算導(dǎo)致的模糊
  2. 默認(rèn)情況下,canvas的抗鋸齒是開(kāi)啟的帅霜,需要關(guān)閉抗鋸齒來(lái)實(shí)現(xiàn)圖像的銳化(MDN: imageSmoothingEnabled )
  3. 除了canvas可以通過(guò)擴(kuò)大2倍寬高然后縮放至原有寬高來(lái)提高清晰度匆背,對(duì)于DOM中其他的元素也可以使用css樣式scale來(lái)實(shí)現(xiàn)同樣的縮放

例: html2canvas配置

convert2canvas() {

    var cntElem = $('#j-sec-end')[0];

    var shareContent = cntElem;//需要截圖的包裹的(原生的)DOM 對(duì)象
    var width = shareContent.offsetWidth; //獲取dom 寬度
    var height = shareContent.offsetHeight; //獲取dom 高度
    var canvas = document.createElement("canvas"); //創(chuàng)建一個(gè)canvas節(jié)點(diǎn)
    var scale = 2; //定義任意放大倍數(shù) 支持小數(shù)
    canvas.width = width * scale; //定義canvas 寬度 * 縮放
    canvas.height = height * scale; //定義canvas高度 *縮放
    canvas.getContext("2d").scale(scale, scale); //獲取context,設(shè)置scale 
    var opts = {
        scale: scale, // 添加的scale 參數(shù)
        canvas: canvas, //自定義 canvas
        // logging: true, //日志開(kāi)關(guān),便于查看html2canvas的內(nèi)部執(zhí)行流程
        width: width, //dom 原始寬度
        height: height,
        useCORS: true // 【重要】開(kāi)啟跨域配置
    };

    html2canvas(shareContent, opts).then(function (canvas) {

        var context = canvas.getContext('2d');
        // 【重要】關(guān)閉抗鋸齒
        context.mozImageSmoothingEnabled = false;
        context.webkitImageSmoothingEnabled = false;
        context.msImageSmoothingEnabled = false;
        context.imageSmoothingEnabled = false;

        // 【重要】默認(rèn)轉(zhuǎn)化的格式為png,也可設(shè)置為其他格式
        var img = Canvas2Image.convertToJPEG(canvas, canvas.width, canvas.height);

        document.body.appendChild(img);

        $(img).css({
            "width": canvas.width / 2 + "px",
            "height": canvas.height / 2 + "px",
        }).addClass('f-full');

    });
}

例: DOM元素樣式:

.targetElem {width: 54px;height: 142px;margin-top:2px;margin-left:17px;transform: scale(0.5)}

三身冀、含有跨域圖片的配置

由于canvas對(duì)于圖片資源的同源限制钝尸,如果畫布中包含跨域的圖片資源則會(huì)污染畫布,造成生成圖片樣式混亂或者h(yuǎn)tml2canvas方法不執(zhí)行等問(wèn)題搂根。

以下主要解決兩類跨域的圖片資源:包括已配置過(guò)CORS的CDN中的圖片資源和微信用戶頭像圖片資源珍促。

3.1 針對(duì)CDN中的圖片的配置

  1. 要求CDN的圖片配置好CORSCDN配置好后剩愧,通過(guò)chrome開(kāi)發(fā)者工具可以看到響應(yīng)頭中應(yīng)含有Access-Control-Allow-Origin的字段猪叙。
  2. 開(kāi)啟html2canvasuseCORS配置項(xiàng)。即作如下設(shè)置:
var opts = {useCORS: true};

html2canvas(element, opts);

注意
如果沒(méi)有開(kāi)啟html2canvasuseCORS配置項(xiàng)仁卷,html2canvas會(huì)正常執(zhí)行且不會(huì)報(bào)錯(cuò)穴翩,但是不會(huì)輸出對(duì)應(yīng)的CDN圖片
(已測(cè)試同時(shí)包含CDN的圖片本地圖片的資源的頁(yè)面,但是只有本地圖片能夠被正常渲染出來(lái))

3.2 針對(duì)微信用戶頭像的配置

如果需要將微信平臺(tái)中的用戶頭像一并保存為圖片锦积,3.1的方案無(wú)能為力芒帕。可通過(guò)配置服務(wù)端代理轉(zhuǎn)發(fā)(forward)實(shí)現(xiàn)丰介,此處不贅述副签。

其他注意事項(xiàng)

1. margin的遮擋問(wèn)題

微信中,喚出長(zhǎng)按保存圖片的菜單要求長(zhǎng)按的對(duì)象直接是<image>元素基矮,如果<image>元素上方存在遮擋淆储,則不會(huì)喚出菜單。
而事實(shí)上家浇,引發(fā)遮擋的并不只是非<image>元素本砰,還可能是margin屬性。例如:若在頁(yè)面底部钢悲,對(duì)一個(gè)絕對(duì)定位的元素設(shè)置了數(shù)值很大的margin-top点额,則margin-top所涉及的區(qū)域,均無(wú)法長(zhǎng)按喚出菜單莺琳。解決方案:將margin-top改用為top即可还棱。

2. 安卓版微信保存圖片失敗的問(wèn)題

canvas2img默認(rèn)保存圖片的格式為png,而在安卓版微信中所生成的圖片盡管能長(zhǎng)按喚出保存圖片的菜單惭等,但是無(wú)法正確保存到本地相冊(cè)珍手。 解決方案:設(shè)置canvas2img的生成圖片格式配置項(xiàng)為jpeg即可。

3. JPEG的黑屏問(wèn)題

設(shè)置canvas2img輸出格式為jpeg,會(huì)有一定幾率導(dǎo)致生成的圖片包含大量的黑色塊琳要。可能的解決方案:縮減部分圖片元素的體積和尺寸大小寡具。

4. 不能保留動(dòng)效

在圖片的轉(zhuǎn)化前,必須停止或者刪除動(dòng)效后才能正確渲染出圖片稚补,否則生成的圖片是破裂的童叠。

參考文獻(xiàn)

轉(zhuǎn)載于:[https://segmentfault.com/a/1190000011478657]

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市课幕,隨后出現(xiàn)的幾起案子厦坛,更是在濱河造成了極大的恐慌,老刑警劉巖乍惊,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杜秸,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡污桦,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門匙监,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)凡橱,“玉大人,你說(shuō)我怎么就攤上這事亭姥〖诠常” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵达罗,是天一觀的道長(zhǎng)坝撑。 經(jīng)常有香客問(wèn)我,道長(zhǎng)粮揉,這世上最難降的妖魔是什么巡李? 我笑而不...
    開(kāi)封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮扶认,結(jié)果婚禮上侨拦,老公的妹妹穿的比我還像新娘。我一直安慰自己辐宾,他們只是感情好狱从,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著叠纹,像睡著了一般季研。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上誉察,一...
    開(kāi)封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天与涡,我揣著相機(jī)與錄音,去河邊找鬼。 笑死递沪,一個(gè)胖子當(dāng)著我的面吹牛豺鼻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播款慨,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼儒飒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了檩奠?” 一聲冷哼從身側(cè)響起桩了,我...
    開(kāi)封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎埠戳,沒(méi)想到半個(gè)月后井誉,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡整胃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年颗圣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屁使。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡在岂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蛮寂,到底是詐尸還是另有隱情蔽午,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布酬蹋,位于F島的核電站及老,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏范抓。R本人自食惡果不足惜骄恶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望匕垫。 院中可真熱鬧叠蝇,春花似錦、人聲如沸年缎。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)单芜。三九已至蜕该,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間洲鸠,已是汗流浹背堂淡。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工馋缅, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人绢淀。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓萤悴,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親皆的。 傳聞我的和親對(duì)象是個(gè)殘疾皇子覆履,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容