將h5頁(yè)面保存成圖片

一矗蕊、需求

最近做了一個(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筝家、保存后的圖片洼裤。


nhj.png

二、功能

由于隱私問題溪王,不能提供上面的詳細(xì)代碼腮鞍,所以下面只做了一個(gè)關(guān)于生成圖片的Demo:點(diǎn)擊“生成圖片”按鈕后,將該按鈕隱藏掉莹菱,同時(shí)可長(zhǎng)按頁(yè)面來保存圖片或者分享等操作移国。

以下是最終完成的頁(yè)面截圖,從左到右依次是:html頁(yè)面道伟、點(diǎn)擊生成圖片后的html頁(yè)面迹缀、保存的圖片使碾。


html2canvas-demo2.png

三、 代碼實(shí)現(xiàn)

1. 方案與思路

  • 通過html2canvas.js祝懂,將Html DOM節(jié)點(diǎn)轉(zhuǎn)換為canvas票摇,Html2Canvas官網(wǎng)
  • 通過CanvasAPItoDataURL砚蓬,將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è)備像素比。

dpr.png

所以可以通過將所有繪制內(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è)問題的):

toDataUrl_bug.png

翻譯一下就是:不能執(zhí)行canvas元素的toDataURL API家夺,因?yàn)楸晃廴镜漠嫴疾荒鼙惠敵觥?br> 究其原因是因?yàn)閏anvas中的圖片跨域了。看解釋

所以可以通過以下方法解決這個(gè)問題:

  1. img設(shè)置crossOrigin屬性為Anonymous伐弹;
  2. 圖片的服務(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):

  1. 生成圖片后,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)度條來掩蓋這種問題)

  2. 生成圖片之后裕寨,用戶看到的是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è)贊哦!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末举娩,一起剝皮案震驚了整個(gè)濱河市析校,隨后出現(xiàn)的幾起案子构罗,更是在濱河造成了極大的恐慌,老刑警劉巖智玻,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遂唧,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡吊奢,警方通過查閱死者的電腦和手機(jī)盖彭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來页滚,“玉大人召边,你說我怎么就攤上這事」郏” “怎么了隧熙?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)幻林。 經(jīng)常有香客問我贞盯,道長(zhǎng),這世上最難降的妖魔是什么滋将? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任邻悬,我火速辦了婚禮,結(jié)果婚禮上随闽,老公的妹妹穿的比我還像新娘父丰。我一直安慰自己,他們只是感情好掘宪,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布蛾扇。 她就那樣靜靜地躺著,像睡著了一般魏滚。 火紅的嫁衣襯著肌膚如雪镀首。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天鼠次,我揣著相機(jī)與錄音更哄,去河邊找鬼。 笑死腥寇,一個(gè)胖子當(dāng)著我的面吹牛成翩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播赦役,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼麻敌,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了掂摔?” 一聲冷哼從身側(cè)響起术羔,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤赢赊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后级历,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體释移,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年鱼喉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了秀鞭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片趋观。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡扛禽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出皱坛,到底是詐尸還是另有隱情编曼,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布剩辟,位于F島的核電站掐场,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏贩猎。R本人自食惡果不足惜熊户,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吭服。 院中可真熱鬧嚷堡,春花似錦、人聲如沸艇棕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沼琉。三九已至北苟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間打瘪,已是汗流浹背友鼻。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留闺骚,地道東北人彩扔。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像葛碧,于是被迫代替她去往敵國(guó)和親借杰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

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