將h5頁面保存成圖片

一、需求

最近做了一個H5頁面,大概內容是:用戶進入H5頁面后做一些測試題,答完測試題后生成一個結果,將這些結果生成一張圖片,用戶可以保存圖片到本地或者分享出去。

其實關鍵點就是在于怎么將Html生成一張圖片,至于圖片的保存分享,微信是自帶這個功能的,長按圖片即可彈出actionsheet來操作。

以下是最終完成的頁面截圖,從左往右依次是:html中的展示、長按html、保存后的圖片。


nhj.png

二、功能

由于隱私問題,不能提供上面的詳細代碼,所以下面只做了一個關于生成圖片的Demo:點擊“生成圖片”按鈕后,將該按鈕隱藏掉,同時可長按頁面來保存圖片或者分享等操作。

以下是最終完成的頁面截圖,從左到右依次是:html頁面、點擊生成圖片后的html頁面、保存的圖片。


html2canvas-demo2.png

三、 代碼實現(xiàn)

1. 方案與思路

  • 通過html2canvas.js,將Html DOM節(jié)點轉換為canvas,Html2Canvas官網(wǎng)
  • 通過CanvasAPItoDataURL,將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.png

所以可以通過將所有繪制內容擴大到像素比倍來使得圖片清晰。

      // 獲取設備的Dpr值
      getDpr: function() {
        if (window.devicePixelRatio && window.devicePixelRatio > 1) {
          return window.devicePixelRatio;
        }
        return 1;
      }
2.使用第三方圖片時會報錯

當直接通過給img src賦值為第三方圖片時,html的渲染及canvas的繪制都是沒有問題的,但是生成圖片時(使用 canvas.toDataURL),會報錯(對于本地的圖片是沒有這個問題的):

toDataUrl_bug.png

翻譯一下就是:不能執(zhí)行canvas元素的toDataURL API,因為被污染的畫布不能被輸出。
究其原因是因為canvas中的圖片跨域了。看解釋

所以可以通過以下方法解決這個問題:

  1. img設置crossOrigin屬性為Anonymous
  2. 圖片的服務端允許跨域(像一些存放圖片元素的服務器,后臺應該是可以配置的,本例中的頭像使用的是本人目前的微信頭像,頁面的二維碼是本地的圖片)

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. 生成圖片

這里有兩個點:

  1. 生成圖片后,Html中二維碼的下面看到的顯示是:A:“長按保存圖片”,但是分享出去的圖片上面顯示的是另外一個文字描述B(這是常見的需求);
    思路:生成圖片之前,將B文字隱藏opacity:0,當要生成圖片的時候,再將B文字顯示opacity:1,生成圖片完成之后,再將B文字隱藏opacity:0。(在文字交換的時候會出現(xiàn)閃爍的問題,可以通過在生成圖片的時候加一個進度條來掩蓋這種問題)

  2. 生成圖片之后,用戶看到的是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

歡迎交流學習。
如果這篇文章有幫到你,記得點個贊哦!

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容

  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML標準。 注意:講述HT...
    kismetajun閱讀 27,559評論 1 45
  • 本文適用人群 需要在微信wap頁開發(fā)分享海報功能的前端程序員們 想要了解html2canvas庫的吃瓜群眾 掙扎在...
    朝顏vivian閱讀 10,184評論 4 17
  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標簽默認的外補...
    _Yfling閱讀 13,776評論 1 92
  • HTML 5 HTML5概述 因特網(wǎng)上的信息是以網(wǎng)頁的形式展示給用戶的,因此網(wǎng)頁是網(wǎng)絡信息傳遞的載體。網(wǎng)頁文件是用...
    阿啊阿吖丁閱讀 3,989評論 0 0
  • 學會使用CSS選擇器熟記CSS樣式和外觀屬性熟練掌握CSS各種選擇器熟練掌握CSS各種選擇器熟練掌握CSS三種顯示...
    七彩小鹿閱讀 6,328評論 2 66