如何将html生成图片并复制到剪切板

1,028 阅读2分钟

1.需求原型

需求原型

2.实现思路

  • 将设计还原成react code
  • 将需要下载的dom绘制成canvas
  • 将canvas转成base64
  • 最后将base64转成blob并复制到剪切板

3.技术选型

经调研,现在市面上有现成成熟的开源软件可以实现关键路径。

注:若有保存图片的需求可以借鉴canvas2image

4.关键代码

/** base64转Blob */
export const b64toBlob = (
  b64Data: string,
  type: string | null = null,
  size: number | null = null,
) => {
  const contentType = type || 'image/png';
  const sliceSize = size || 512;
  const byteCharacters = window.atob(b64Data.substring(b64Data.indexOf(',') + 1));
  const byteArrays = [];
  for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    const slice = byteCharacters.slice(offset, offset + sliceSize);
    const byteNumbers = new Array(slice.length);
    // eslint-disable-next-line no-plusplus
    for (let i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }
    const byteArray = new Uint8Array(byteNumbers);
    byteArrays.push(byteArray);
  }
  return new Blob(byteArrays, { type: contentType });
};


/** 复制图片到剪切板
 * b64Data-base64
 */
export const imageClip = (b64Data: string, callBack?: () => void) => {
  const item = new clipboard.ClipboardItem({
    'image/png': b64toBlob(b64Data, 'image/png', 512),
  });
  clipboard.write([item]).then(() => {
    if (callBack) {
      callBack();
    }
  });
};

 /** canvas配置 解决模糊问题 
 * 思路:通过把canvas容器扩大,再将和成的图片进行缩放
 */
  const canvasConfig = useCallback((dom: HTMLElement) => {
    const box = window.getComputedStyle(dom);
    // DOM 节点计算后宽高
    const width = parseInt(box.width, 10);
    const height = parseInt(box.height, 10);
    // 获取像素比
    const scaleBy = 2;
    // 创建自定义 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);
    return { canvas };
  }, []);

/** 复制二维码  * 
const handleCopyQRCode = useCallback(() => {
  const dom = document.getElementById('_downQRCode');
  if (dom) {
    html2canvas(dom as HTMLElement, canvasConfig(dom)).then((imgDom) => {
      const base64 = imgDom.toDataURL();
      imageClip(base64, () => {
        Toast.success('已复制到剪切板', 2000);
      });
    });
  }
}, [canvasConfig]);

5.在火狐、360兼容模式、IE等浏览器复制到剪切板有兼容性问题,可以换成下载思路。

/** 保存png */
export const saveAsPNG = (b64Data: string, fileName: string = 'download') => {
  // 下载base64图片
  const blob = b64toBlob(b64Data, 'image/png', 512);
  // @ts-ignore
  if (window?.navigator?.msSaveOrOpenBlob) {
    // @ts-ignore
    window.navigator.msSaveOrOpenBlob(blob, `${fileName}.png`);
  } else {
    const aLink = document.createElement('a');
    const evt = document.createEvent('HTMLEvents');
    evt.initEvent('click', true, true); // initEvent 不加后两个参数在FF下会报错  事件类型,是否冒泡,是否阻止浏览器的默认行为
    aLink.download = fileName;
    aLink.href = URL.createObjectURL(blob);
    aLink.click();
  }
};