使用html2canvas、jQuery结合实现PC端绘制canvas图并下载

2,187 阅读6分钟

前言:

  • 文本虽然所提到的是在jQuery中使用,但是在vue等框架中实现PC端绘制canvas图并下载依然是适用的。
  • 本文使用的html2canvas版本是:1.0.0-rc.7
  • 本文接口返回采用的图片地址格式是base64的,主要是为了兼容所有浏览器,而且下载速度挺快。若不想如此粗暴,让性能体验更佳,则可以分接口处理,但是注意IE相关的浏览器是不兼容的哦;如谷歌等浏览器可以处理成图片url地址进行返回,IE相关的浏览器处理为另外一个接口则返回base64格式的。

贴出本作者处理的具体代码如下:

/* 判断是否是isIE11或isEdge或isLessIE11 */
function isIE11Fn() {
    var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串
    var isLessIE11 = userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1;
    // 判断是否为IE的Edge浏览器
    var isEdge = userAgent.indexOf("Edge") > -1 && !isLessIE11;
    // 判断是否为IE11浏览器
    var isIE11 = userAgent.indexOf("Trident") > -1 && userAgent.indexOf("rv:11.0") > -1;

    return isLessIE11 || isIE11 || isEdge;
};
// 请求接口,举例子:
var api = "xxx接口url地址"; // 返回
if (isLessIE11 || isIE11 || isEdge) {
  api += "&is_base64image=1"; // 返回base64格式图片
}
// 请求接口
jqueryAjaxGet(api, null, function (res) {
    // 里边进行相关的逻辑处理等
    if (res.status == 1) {
        _this.poster = res.data;
        var imgarr = [_this.poster.background_img];
        if(_this.poster.goods_list.length) {
          $.each(_this.poster.goods_list, function(i, item){
            imgarr.push(item.goods_image);
          })
        }
        downloadImg(".poster_con", 'poster', imgarr);
    }
});

一、安装

script形式引入“html2canvas.min.js”,实际路径按项目的来(记得修改路径~)

script src="xxx/..../common/html2canvas.min.js"></script>

二、使用

封装成公共的方法进行使用

/**
 * @Author: acaiEncode 
 * @File: 保存图片or保存海报
 * @param {*} canvasImgClass  截图的包裹的dom对象(原生)
 * @param {*} imgName 图片名称
 * @param {*} imgArr dom图片数组
 * 提示:前提需要先引入html2canvas.js,版本号:1.0.0-rc.7
 * 使用:downloadImg(".poster_con", 'poster', imgarr);
 */
 function downloadImg(canvasImgClass, imgName, imgArr) {
    loading.showloading();
    var canEle = $(canvasImgClass)[0];   // 获取截图的包裹的dom对象(原生)
    var width = canEle.offsetWidth; // 获取dom宽
    var height = canEle.offsetHeight;   // 获取dom高
    var canvas = document.createElement('canvas');  // 创建一个canvas节点
    // var scale = height / (height * window.devicePixelRatio); // 定义任意放大倍数 支持小数
    var scale = window.devicePixelRatio; // 定义任意放大倍数 支持小数
    canvas.width = width * scale; // 定义canvas 宽度 * 缩放
    canvas.height = height * scale; // 定义canvas高度 *缩放
    var content = canvas.getContext("2d");
    content.scale(scale, scale); //获取context,设置scale 
    // var rect = canEle.getBoundingClientRect(); //获取元素相对于视察的偏移量
    // content.translate(-rect.left, -rect.top); //设置context位置,值为相对于视窗的偏移量负值,让图片复位
    var options = {
        scale: scale, // 添加的scale 参数
        canvas: canvas, // 自定义 canvas
        width: width, // dom 原始宽度
        height: height,
        x: window.pageXOffset, // 裁剪画布X坐标
        y: window.pageYOffset,
        // foreignObjectRendering: true, // 最主要是这句话,官方给出解释是否在浏览器支持的情况下使用ForeignObject渲染,
        // scrollX: 0, // 渲染元素时要使用的x滚动位置(例如,如果Element使用position: fixed)
        // scrollY: 0,
        tainttest: true, // 检测每张图片都已经加载完成
        useCORS: true // 【重要】开启跨域配置
    };
    // 重点:接口图片返回较多时需等图片加载完成才进行截图描绘
    if(imgArr && imgArr.length) {
        var img = [],
        flag = 0,
        mulitImg = imgArr;
        var imgTotal = mulitImg.length;
        for (var i = 0; i < imgTotal; i++) {
            img[i] = new Image()
            img[i].onload = function () {
                //第i张图片加载完成
                flag++
                if (flag == imgTotal) {
                    // 全部加载完成,进行画布的裁剪
                    getCanvas(canvas,canEle,options,imgName)
                }
            }
            img[i].src = mulitImg[i];
        }
    } else {
        // 画布的裁剪
        getCanvas(canvas,canEle,options,imgName)
    }
};
/**
 * 画布生成后下载图片
 *
 * @param {*} canvas 画布
 * @param {*} canEle dom对象
 * @param {*} options 配置项
 * @param {*} imgName 图片名称
 */
function getCanvas(canvas, canEle, options, imgName) {
    var canvas = canvas;
    html2canvas(canEle,options).then(function(canvas) {
        var context = canvas.getContext('2d');
        // 关闭抗锯齿 保证生成的分享图是清晰的
        context.mozImageSmoothingEnabled = false;
        context.webkitImageSmoothingEnabled = false;
        context.msImageSmoothingEnabled = false;
        context.imageSmoothingEnabled = false;
        var imgDataUrl = canvas
        .toDataURL("image/" + ".jpg")
        .replace("image/" + ".jpg", "image/octet-stream"); // 得到图片base64编码数据
        if (window.navigator.msSaveOrOpenBlob) {
            // 允许用户在客户端上保存文件
            var bstr = atob(imgDataUrl.split(",")[1]);
            var n = bstr.length;
            var u8arr = new Uint8Array(n);
            while (n--) {
              u8arr[n] = bstr.charCodeAt(n);
            }
            var blob = new Blob([u8arr]);
            window.navigator.msSaveOrOpenBlob(blob, imgName + "." + "jpg");
          } else {
            // 这里就按照chrome等新版浏览器来处理保存图片
            var a = document.createElement("a");
            a.href = imgDataUrl;
            // a.id = 'imgDownBtn';
            a.setAttribute("download", imgName+".jpg");
            a.click();
            $().remove(a);
            // document.remove($('#imgDownBtn'));
          }
          loading.hideloading();
    });
}
// loading
var loading = {
    showloading: function (msg) {
        var hadMask = $("body div").hasClass(".win-mask");
        if (hadMask) {
            $(".win-mask").show();
        } else {
            loading.innerloading(msg);
        }
    },
    hideloading: function () {
        $(".win-mask").remove();
    },
    innerloading: function (msg) {
        var def_msg = "";
        def_msg = msg ? msg : '正在加载中...';
        $("body").append('<div class="win-mask">\
                              <div class="loading-box">\
                                  <div class="loadings"></div>&nbsp;&nbsp;' + def_msg + '\
                              </div>\
                          </div>');
    }
}

具体页面上的详细使用

// html

<section id="liveShareWin">
  <div class="commonPopup" style="z-index: 999; display: none"></div>
  <div class="commonPopup-container" style="z-index: 8000; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); display: none; margin-left: -500px; margin-top: -296.5px;">
    <div class="commonPopup-header">
      <h3>分享</h3>
      <div class="close-img commonPopup-close-js"></div>
    </div>
    <div class="commonPopup-body">
      <div class="row">
        <div class="col-md-6 leftBox">
          <h4>直播间小程序码</h4>
          <div class="liveCodeImg">
            <img src="" alt="liveCode">  
          </div>
          <div>
            <!-- <button class="saaveCodeBtn imgSaveBtn">保存图片</button> -->
            <a class="saveCodeBtn imgSaveBtn" download="直播间小程序码.jpg">保存图片</a>
          </div>
        </div>
        <div class="col-md-6 rightBox">
          <h4>直播间分享海报</h4>
          <div class="posterShare">
            <div class="bannerImg">
              <img src="" alt="banner">  
            </div>
            <h6></h6>
            <div class="footerContent row">
              <div class="col-md-8">
                <p class="timesBox"></p>
                <p class="tips">长按识别小程序码,观看直播</p>  
              </div>
              <div class="col-md-4">
                <div class="miniCode">
                  <img src="" alt="miniCode">
                </div>
              </div>
            </div>
          </div>
          <div>
            <button class="savePosterBtn imgSaveBtn">保存图片</button>
          </div>
        </div>
      </div>
    </div>
  </div>
</section>
// css

#liveShareWin .row>div {
    text-align: center;
}

#liveShareWin .row>.rightBox::after {
    content: '';
    display: inline-block;
    border-right: 1px dashed #ccc;
    height: 50%;
    position: absolute;
    top: 94px;
    left: 0;
}

#liveShareWin h4 {
    font-size: 18px;
    color: #333;
    font-weight: bold;
    padding-top: 40px;
}

.liveCodeImg {
    width: 180px;
    height: 180px;
    margin: 0 auto;
    margin-top: 40px;
}

.liveCodeImg>img {
    width: 100%;
    height: 100%;
}
#beginnerGuide .openQuliyBtn,
#liveShareWin .imgSaveBtn {
    display: inline-block;
    width: 160px;
    height: 40px;
    font-size: 16px;
    color: #fff;
    background-color: #fe5043;
    border-radius: 5px;
}

.saveCodeBtn {
    margin-top: 60px;
    line-height: 40px;
}

.posterShare {
    width: 300px;
    /* height: 395px; */
    box-shadow: rgba(0, 0, 0, .13) 0px 0px 13px 1px;
    margin: 0 auto;
    margin-top: 20px;
}

.bannerImg {
    width: 300px;
    height: 240px;
}
.bannerImg>img {
    width: 100%;
    height: 100%;
}

.savePosterBtn {
    margin-top: 30px;
}

.posterShare h6 {
    margin: 20px 10px;
    font-size: 16px;
    text-align: left;
    height: 40px;    /* 一定要固定高度,火狐和ie浏览器超过省略,但是点点不会出来 */ 
    line-height: 20px;
    word-break: break-all;
    /* white-space: nowrap; */
    overflow: hidden;
    /* text-overflow: ellipsis; */
}
.footerContent {
    padding: 20px 10px;
}

.footerContent>div {
    padding: 0;
}

.footerContent p {
    text-align: left;
    font-size: 14px;
    padding-top: 20px
}

.footerContent p.tips {
    font-size: 12px;
    padding-top: 10px
}

.footerContent p.tips::after {
    content: '';
    display: block;
    width: 12px;
    height: 2px;
    background-color: red;
    margin-top: 5px;
}

/* .col-md-8 {
    border-right: 1px dashed #fedada;
} */

.miniCode {
    width: 62px;
    height: 62px;
    margin: 6px auto;
    margin-left: 20px;
}
.miniCode>img {
    width: 100%;
    height: 100%;
}
// js
var room_mini_code = '';
var miniCodeArr,
poseterBannerArr = [];
$('.liveManage .tbody').on('click', '.shareBtn', function() {
    var room_id = $(this).data('roomid');
    jqueryAjaxPost(api.shareLive, {room_id: room_id}, function (res) {
        miniCodeArr,
        poseterBannerArr  = [];
        if(res.status) {
            var res = res.data;
            miniCodeArr = [res.room_mini_code];
            poseterBannerArr = [res.room_mini_code, res.share_img];
            room_mini_code = res.room_mini_code;
            $('#liveShareWin .liveCodeImg>img').prop('src', res.room_mini_code);
            // $('#liveShareWin .saveCodeBtn').prop('href', res.room_mini_code); // 直播间小程序码

            $('#liveShareWin .miniCode>img').prop('src', res.room_mini_code);
            $('#liveShareWin .bannerImg>img').prop('src', res.share_img);
            $('#liveShareWin .posterShare>h6').html(res.name);
            var times = res.start_time + '&nbsp;-&nbsp;' + res.end_time;
            $('#liveShareWin .footerContent .timesBox').html(times);
            liveShareWin.show();
        } else {
            alert(res.msg);
        }
    });
});
// 保存图片-download
$('#liveShareWin .saveCodeBtn').on('click', function() {
    // (-html2canvas实现方式)
//     downloadImg('#liveShareWin .liveCodeImg', '小程序码', miniCodeArr);
    if(room_mini_code) {
        init.compADown(room_mini_code, '直播间小程序码');
    }
})
$('#liveShareWin .savePosterBtn').on('click', function() {
    downloadImg('#liveShareWin .posterShare', '海报', poseterBannerArr);
    // liveShareWin.hide();
})

效果图:

poster_eg.png

三、遇到bug及对策

1、在jQuery中引入使用html2vanvas.js,在IE浏览器会有出现一个报错:SCRIPT5009: “Promise”未定义

error5.png

解决方案:引入es6-promise.auto.min.js插件进行处理。

2、jQuery配合html2canvas 使用时 报错 Uncaught (in promise) Provided element is not within a Document

error11.png

原因是:html2canvas接收的是 一个 js DOM 元素而不是 一个 jQuery DOM对象。

var canEle = $(canvasImgClass); // jquery 获取元素

解决思路:

var canEle = $(canvasImgClass)[0];   // 获取截图的包裹的dom对象(原生)

3、使用img = new Image(); img.onload = function() {}后html2canvas出现以下报错:

error12.png

配置项加入:

foreignObjectRendering: true, // 最主要是这句话,官方给出解释是否在浏览器支持的情况下使用ForeignObject渲染,

报错虽然解决,但是出现另外的问题:图片无法显示,绘图错乱。

error13.png

经过排查,发现主要原因是:由如下代码导致图片还在onload,而画布还没有开始画,就隐藏了,因此画布就没有高度/宽度,以至于报错,注释后,则没有报错了。

$("#poster_body").attr("style", "display:none;");

因此得出结论:必须是dom可视才能很好的进行使用html2canvas截图生成海报!

4、html2canvas对两行省略不识别,会直接丢弃,出来空白的结果;而一行省略的话,点点也是不识别的,会直接切掉文字

解决方案:

(1)一定要省略的话就用伪类做成点点;

(2)修改方案,不要省略符。

5、html2canvas对虚线无效,会把虚线识别为实线展示出来

解决方案参考:

html2canvas 实现dashed虚线边框

html2canvas 虚线渲染为实心的問題

解决方案(本文采用的方案):不用虚线或者改为实线!(一定要使用需要用虚线的客官,可以尝试贴出来解决的方案)。

6、弹窗使用html2canvas后一直都是空白的!

原因是:截取区域在弹窗

解决方案:

options.png

番外:移动端使用html2canvas进行生成海报并下载图片

1、pc端所使用的方式在移动端在使用时,出现不适配兼容所有移动端的各个浏览器的问题。

目前qq打开h5页面,则前提需要安装qq浏览器,则可以进行下载,UC等类型的浏览器可以下载;

微信内置浏览器不兼容,原因其不支持下载文件的原因,要解决此问题。

具体的解决方案可参考以下:(主要是通过跳转第三方浏览器即可使用)

(1)微信内置浏览器不支持下载文件应用的解决方法

(2)微信内置浏览器不支持下载的解决方案 微信点击链接直接下载app安装包功能实现方式

(3)使用h5 标签 href='url' download 下载踩过的坑

其次,h5内嵌到自己的app上也有问题:安卓能够进行下载图片,前提是要传生成好的图片过去,但是安卓是不能够下载的,只能长按保存图片!总结如下:

Android 可以点击下载保存

IOS 只能长按保存

2、因此鉴于以上所描述的种种问题,若移动端想要实现海报的下载图片功能,又能够友好的兼容移动端端各个端,采用的最佳方案就是把html2canvas生成的海报图片,展示在页面上,引导用户进行长按保存图片。

效果案例图:

banner2.png

具体代码参考: [html2canvas在vue2中的应用-移动端](html2canvas在vue2中的应用-移动端 #掘金文章# juejin.cn/post/698387…)