將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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容