关于H5长按保存图片的实现——基于html2canvas.js

2,040 阅读3分钟

需求

在微信中打开的活动推广页面,点击保存图片的按钮将图片保存在手机相册中。

首先主要功能实现是从这篇文章中学到的

链接:juejin.cn/post/684490… 作者:丁香园F2E

以下是一些文章中没提到或者是没完善代码的部分。

方案

html -> canvas -> image ->手动长按图片保存

  • html2canvas.js:可将 htmldom 转为 canvas 元素。传送门:github.com/niklasvh/ht…
  • canvasAPI:toDataUrl() 可将 canvas 转为 base64 格式
  • 手动长按图片保存

实施

import html2canvas from 'html2canvas';

  • 遇到的问题

图片模糊
  1. 不要使用background-image,需要生成截图的图片都要使用image标签渲染。html2canvas截图如果使用背景图片就会导致生成的图片模糊。

  2. 根据window.devicePixelRatio获取像素比


/**
 * 根据window.devicePixelRatio获取像素比
 */
function DPR() {
    if (window.devicePixelRatio && window.devicePixelRatio > 1) {
        return window.devicePixelRatio;
    }
    return 1;
}
/**
 *  将传入值转为整数
 */
function parseValue(value) {
    return parseInt(value, 10);
};
/**
 * 绘制canvas
 */
async function drawCanvas(selector) {
    // 获取想要转换的 DOM 节点
    const dom = document.querySelector(selector);
    const box = window.getComputedStyle(dom);
    // DOM 节点计算后宽高
    const width = parseValue(box.width);
    const height = parseValue(box.height);
    // 获取像素比
    const scaleBy = DPR();
    // 创建自定义 canvas 元素
    const 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`;
    // 获取画笔
    const context = canvas.getContext('2d');

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

    // 将自定义 canvas 作为配置项传入,开始绘制
    return await html2canvas(dom, {canvas});
}


图片不完整

1.为了保证html2canvas截图的完整性,避免出现截图有空白部分。要确保页面滚动条置于顶点,或者置于需要截图部分的水平线

    window.pageYOffset = 0;
    document.documentElement.scrollTop = 0;
    document.body.scrollTop = 0;

2.需要生成的图片中存在网络图片,网络图片有跨域的问题。 原因,排查后发现是因为 canvas 内的图片跨域了 这里有解释总而言之,就是:可以在 canvas 中绘制跨域的图片,但此时的 canvas 处于被 「污染」 的状态,而污染状态的 canvas 使用 toDataUrl() 等 API 是会出现问题的。

这一点需要前后端配合解决:

  1. 给 img 元素设置 crossOrigin 属性,值为 anonymous
  2. 图片服务端设置允许跨域(返回 CORS 头)

但是并不是说解决了这两点,就可以完美的拿到图片并整合成canvas了……还是有可能画不出图片。 这个时候可以这样处理,在进行html2canvas之前,请求网络图片,把图片转换成base64格式,用这个格式去重新渲染dom上的img标签:

完整代码



  getBas64 = (url, outputFormat = 'image/png') => {
    return new Promise(function(resolve, reject) {
      let canvas = document.createElement('CANVAS'),
        ctx = canvas.getContext('2d'),
        img = new Image();

      img.crossOrigin = 'Anonymous'; // 重点!设置image对象可跨域请求
      img.onload = function() {
        canvas.height = img.height;
        canvas.width = img.width;
        ctx.drawImage(img, 0, 0);
        let dataURL = canvas.toDataURL(outputFormat);
        canvas = null;
        resolve(dataURL);
      };
      img.src = url;
    });
  };


 let imgList = []; //要请求的图片集合,格式为{id:domId,value:url}
 
  Promise.all(imgList.map((item) => this.getBas64(item.value))).then(
      (result)=>{
        // 返回的图片列表,重新渲染到页面上
        result.forEach((v, k) => {
          const id = 'medals' + k;
          let img = document.getElementById(id);
          img.src = v;
        });

        const dom = document.querySelector('#saveImg');
        window.pageYOffset = 0;
        document.documentElement.scrollTop = 0;
        document.body.scrollTop = 0;

        const box = window.getComputedStyle(dom);
        // DOM 节点计算后宽高
        const width = parseValue(box.width);
        const height = parseValue(box.height);
        // 获取像素比
        const scaleBy = DPR();
        // 创建自定义 canvas 元素
        const canvas = document.createElement('canvas');

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

        // canvas.width = window.screen.availWidth;
        // canvas.height = window.screen.availHeight;

        // canvas.x = 0;
        // canvas.y = window.pageYOffset;

        // canvas.windowHeight = document.body.scrollHeight;
        // canvas.windowWidth = document.body.scrollWidth;
        // 设定 canvas css宽高为 DOM 节点宽高
        canvas.style.width = `${width}px`;
        canvas.style.height = `${height}px`;
        // 获取画笔
        const context = canvas.getContext('2d');
        context.mozImageSmoothingEnabled = false;
        context.webkitImageSmoothingEnabled = false;
        context.msImageSmoothingEnabled = false;
        context.imageSmoothingEnabled = false;

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


        const opts = {
          scale: scaleBy, // 添加的scale 参数
          canvas: canvas, //自定义 canvas
          // logging: true, //日志开关,便于查看html2canvas的内部执行流程
          width: width, //dom 原始宽度
          height: height,
          useCORS: true, // 【重要】开启跨域配置
          allowTaint: false,
        };
        html2canvas(dom, { opts }).then((canvas) => {
          let imgUrl = canvas.toDataURL('image/png');
          this.setState({
            dataUrl: imgUrl,
          });
        });
      }
    );