H5生成海报并保存

4,243 阅读2分钟

1.前言

最近产品提了一个内部需求,为了产品的快速推广,需要给内部市场部门提供一个 H5 网页,内置多个海报背景,预留一个上传二维码的区域,方便给市场部门上传自定义的二维码并生成多种海报样式。

该需求还是比较简单的,在站内也能查到各种资料,但是万万没想到,就算照着抄作业都还能踩着坑。


2. 实现逻辑梳理:

  • 构建页面,并将要渲染的 dom 脱离文档流
  • 获取像素比 dpr
  • 上传二维码图片并将图片转为 base64 本地预览
  • 使用 html2canvasdom 渲染到 canvas
  • 利用像素比计算图片倍率,方便生成图片
  • 生成图片并利用微信浏览器长按保存功能将图片渲染在页面

3. 代码实现

<div class="view">
  <img src="" alt="" class="bg">
  <img src="" alt="" class="qr-code">
</div>
<!-- 只允许上传图片格式文件 -->
<input type="file" class="file" accept="image/*"> 
// 先抄个作业
// 获取像素比
function DPR() {
    if (window.devicePixelRatio && window.devicePixelRatio > 1) {
        return window.devicePixelRatio;
    }
    return 1;
}
// 将传入值转为 10 进制整数
function parseValue(value) {
    return parseInt(value, 10);
}
// 绘制 canvas
function drawCanvas(selector) {
    // 获取想要转换的 DOM 节点
    var dom = document.querySelector(selector);
    var box = window.getComputedStyle(dom);
    // DOM 节点计算后宽高
    var width = parseValue(box.width);
    var height = parseValue(box.height);
    // 获取像素比
    var scaleBy = DPR();
    // 创建自定义 canvas 元素
    var canvas = document.createElement('canvas');
    
    // 设定 canvas 元素属性宽高为 DOM 节点宽高 * 像素比
    canvas.width = width * scaleBy + 1;
    canvas.height = height * scaleBy;
    // 设定 canvas css宽高为 DOM 节点宽高
    canvas.style.width = `${width * scaleBy + 1}px`;
    canvas.style.height = `${height * scaleBy}px`;
    
    // 背景透明,这里设置透明其实是无效的,需要在 options 里面单独配置
    canvas.style.backgroundColor = 'rgba(0,0,0,0)';
    // 获取画笔
    var context = canvas.getContext('2d');
    context.fillStyle = 'rgba(0,0,0,0)';
    
    // 将所有绘制内容放大屏幕倍率
    // 因为UI提供了海报图的不同倍率图片,这段代码就注释了
    // context.scale(scaleBy, scaleBy);
    
    // 将自定义 canvas 作为配置项传入,开始绘制
    return html2canvas(dom, {
      canvas
    });
}
// 作业抄完

// 根据屏幕倍率渲染不同倍率的图片
document.querySelector('.bg').setAttribute('src', './image/pic_1x'+DPR()+'.png');

// 选择图片并本地预览功能
// file 标签选择二维码图片并填充到页面预留容器中
document.querySelector('.file').onchange = function() {
    var file = this.files[0],
        reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = (e) => {
        var code = document.querySelector('.qr-code')
        code.setAttribute('src', e.target.result);
        // 重置 input file 因为有多个海报样式选择,这里是为了选择相同的文件再次触发 change 事件
        this.val(''); 
    }
}

// 给二维码容器添加 onload 事件,触发 drawCanvas 方法
document.querySelector('.qr-code').onchang = function() {
    var canvas = drawCanvas('.view'),
        wrap = document.querySelector('.view');
    canvas.then((c) => {
        // 调用 canvasAPI 的 toDataURL 方法,生成 base64
        var base64 = c.toDataURL('image/png');
        // 将容器内容替换为生成的图片
        wrap.innerHTML = `<img src="${base64}" >`
    })
}

遇到的问题

  1. 生成的海报图本来是圆角 png 图片,但是居然有白边,图片不透明
// 解决方案
html2canvas(dom, {
    useCORS: true,
    allowTaint: false,
    // canvas,
    // 这里是生成 png 是否为透明的关键,
    // 不然 html2canvas 会自动将背景设为白色,生成的 png 图片就会在透明的位置自带白色
    backgroundColor: null,
})

4.参考资料

参考资料:
一次 H5 「保存页面为图片」 的踩坑之旅