将h5页面保存成图片

一、需求

最近做了一个H5页面,大概内容是:用户进入H5页面后做一些测试题,答完测试题后生成一个结果,将这些结果生成一张图片,用户可以保存图片到本地或者分享出去。

其实关键点就是在于怎么将Html生成一张图片,至于图片的保存分享,微信是自带这个功能的,长按图片即可弹出actionsheet来操作。

以下是最终完成的页面截图,从左往右依次是:html中的展示、长按html、保存后的图片。


nhj.png

二、功能

由于隐私问题,不能提供上面的详细代码,所以下面只做了一个关于生成图片的Demo:点击“生成图片”按钮后,将该按钮隐藏掉,同时可长按页面来保存图片或者分享等操作。

以下是最终完成的页面截图,从左到右依次是:html页面、点击生成图片后的html页面、保存的图片。


html2canvas-demo2.png

三、 代码实现

1. 方案与思路

  • 通过html2canvas.js,将Html DOM节点转换为canvas,Html2Canvas官网
  • 通过CanvasAPItoDataURL,将canvas转换为 Base64的格式,并将它设置为img src属性值
  • 在微信浏览器中,长按img,会弹起actionsheet,可以进行保存、发送、识别二维码等操作。(注意:不要给图片设置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

翻译一下就是:不能执行canvas元素的toDataURL API,因为被污染的画布不能被输出。
究其原因是因为canvas中的图片跨域了。看解释

所以可以通过以下方法解决这个问题:

  1. img设置crossOrigin属性为Anonymous
  2. 图片的服务端允许跨域(像一些存放图片元素的服务器,后台应该是可以配置的,本例中的头像使用的是本人目前的微信头像,页面的二维码是本地的图片)

code演示:将需要渲染的第三方图片转为Base64的格式并设置crossOrigin属性,赋值给需要展示的img src

      // 将图片转为base64格式
      img2base64: function(url, crossOrigin) {
        // 这里使用了 ES6 的Promise,及箭头函数
        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编码数据
            resolve(c.toDataURL('image/png'));
          };

          // 结合合适的CORS响应头,实现在画布中使用跨域<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。(在文字交换的时候会出现闪烁的问题,可以通过在生成图片的时候加一个进度条来掩盖这种问题)

  2. 生成图片之后,用户看到的是html的内容,但是长按的时候其实是在图片上操作。
    思路:生成图片之后,将生成的图片展示在最上层,并设置opacity:0,这样用户长按的就是这张透明度为0的图片了。

generateImage: function() {
        var _this = this;

        var scanTextElem = document.getElementById('scanText');
        scanTextElem.style.opacity = '1';

        // 获取想要转换的dom节点
        var dom = document.getElementById('app');
        var box = window.getComputedStyle(dom);

        // dom节点计算后宽高
        var width = _this.parseValue(box.width);
        var height = _this.parseValue(box.height);

        // 获取像素比
        var scaleBy = _this.getDpr();

        // 创建自定义的canvas元素
        var canvas = document.createElement('canvas');

        // 设置canvas元素属性宽高为 DOM 节点宽高 * 像素比
        canvas.width = width * scaleBy;
        canvas.height = height * scaleBy;

        // 设置canvas css 宽高为DOM节点宽高
        canvas.style.width = width + 'px';
        canvas.style.height = height + 'px';

        // 获取画笔
        var context = canvas.getContext('2d');

        // 将所有绘制内容放大像素比倍
        context.scale(scaleBy, scaleBy);

        // 设置需要生成的图片的大小,不限于可视区域(即可保存长图)
        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数据
          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的宽高,也就是可视区域的宽高

      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

欢迎交流学习。
如果这篇文章有帮到你,记得点个赞哦!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML标准。 注意:讲述HT...
    kismetajun阅读 27,406评论 1 45
  • 本文适用人群 需要在微信wap页开发分享海报功能的前端程序员们 想要了解html2canvas库的吃瓜群众 挣扎在...
    朝颜vivian阅读 10,148评论 4 17
  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,725评论 1 92
  • HTML 5 HTML5概述 因特网上的信息是以网页的形式展示给用户的,因此网页是网络信息传递的载体。网页文件是用...
    阿啊阿吖丁阅读 3,828评论 0 0
  • 学会使用CSS选择器熟记CSS样式和外观属性熟练掌握CSS各种选择器熟练掌握CSS各种选择器熟练掌握CSS三种显示...
    七彩小鹿阅读 6,304评论 2 66