利用 html2canvas 将指定元素转成图片进行打印或下载。但是元素区域中包含 svg,svg图像无法被 html2canvas 绘制出来。因此,需要借助 canvg 插件实现。

"canvg" : "^4.0.1" , "html2canvas" : "^1.4.1" , "print-js" : "^1.6.0" , "vue" : "^3.2.41" ,
import html2canvas from "html2canvas";
import { Canvg } from "canvg";
import print from "print-js";
import { nextTick } from "vue"; // 所有使用 nextTick的地方 都可以用 setTimeout 代替
const html2canvasOptions = {
  useCORS: true,
  scale: window.devicePixelRatio < 2 ? 2 : window.devicePixelRatio,
  tainttest: true, // 检测每张图片已经加载完成
  isDownload: false,
const html2canvasFn = (
  cloneEl: HTMLElement,
  options: any,
  originEl: Element
) => {
  return new Promise((resolve, reject) => {
    html2canvas(cloneEl, options).then((canvas) => {
      const base64File = canvas.toDataURL("image/png");
      const blob = base64ToFile(base64File);
      const url = URL.createObjectURL(blob);
      if (options.isDownload) {
        dowloadFn(url);
      } else {
        printFn(base64File, cloneEl);
      nextTick(() => {
        URL.revokeObjectURL(url);
        originEl.removeChild(cloneEl);
        resolve(true);
const base64ToFile = (dataUrl: string) => {
  const arr = dataUrl.split(",");
  const mime = arr[0].match(/:(.*?);/)![0];
  const bstr = atob(arr[1]);
  let n = bstr.length;
  const u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  return new Blob([u8arr], { type: mime });
const dowloadFn = (url: string) => {
  const a = document.createElement("a");
  a.setAttribute("href", url);
  a.setAttribute("download", new Date().getTime() + ".png");
  a.click();
  nextTick(() => {
    a.remove();
const printFn = (url: string, parentEl: HTMLElement) => {
  const id = "_printImg";
  const img = document.createElement("img");
  img.src = url;
  img.id = id;
  parentEl.appendChild(img);
  onPrint(id);
  nextTick(() => {
    parentEl.removeChild(img);
const onPrint = (elId: string) => {
  print({
    printable: elId,
    type: "html",
    scanStyles: false,
const printContentById = (elId: string, options: any) => {
  return new Promise((resolove, reject) => {
    const originEl = document.querySelector(elId);
    if (originEl) {
      const cloneEl = originEl.cloneNode(true) as HTMLElement;
      originEl.appendChild(cloneEl);
      svg2Canvas(cloneEl).then((res) => {
        html2canvasFn(
          cloneEl,
          Object.assign({}, html2canvasOptions, options),
          originEl
        ).then((res) => {
          // 延迟设置完成状态
          setTimeout(() => {
            resolove(true);
          }, 1000);
    } else {
      console.error("未找到打印元素");
      reject(false);
const svg2Canvas = (cloneEl: HTMLElement) => {
  return new Promise((resolve, reject) => {
    let index = 0;
    let elLength = 0;
    const callback = () => {
      if (index === elLength) {
        resolve(true);
    try {
      const svgEls = cloneEl.querySelectorAll("svg");
      const imgEls = cloneEl.querySelectorAll("img");
      const els = [...Array.from(svgEls), ...Array.from(imgEls)];
      elLength = els.length;
      els.forEach(async (el) => {
        const parentNode = el.parentNode;
        const canvas = document.createElement("canvas");
        canvas.style.zIndex = "0";
        if (el.tagName === "svg") {
          const svg = el.outerHTML.trim();
          const { width, height } = el.getBoundingClientRect();
          canvas.width = width;
          canvas.height = height;
          const ctx = canvas.getContext("2d");
          const v = await Canvg.from(ctx!, svg);
          if (el.style.position) {
            canvas.style.position = el.style.position;
            canvas.style.left = el.style.left;
            canvas.style.top = el.style.top;
          v.start(); // 在页面上绘制 转换好的 canvas图像
          index++;
          callback();
        if (el.tagName === "IMG") {
          canvas.width = el.width as number;
          canvas.height = el.height as number;
          canvas.getContext("2d")!.drawImage(el as any, 0, 0);
          index++;
          callback();
        parentNode!.appendChild(canvas);
        parentNode!.removeChild(el);
    } catch (error) {
      reject(error);
    jyp20121107