在 vue3.x 使用 dom-to-image 结合 print-js 封装一个打印 hook

588 阅读3分钟

背景

在业务开发过程中,会有遇到打印 dom 元素的需求,一般来说的都是采用 html2canvas + print-js实现。

既然是介绍一个全新的方案,那么我们就先来看看 dom-to-image + print-js 的实现方式,并且结合 vue3.x 封装为一个通用 hooks

前言

为什么会选择 dom-to-image 去替换 html2canvas?其实也是考虑到该库的体积与其优秀的特性,可以更加友好的支持更多 css 特性。具体可以查看官网查阅细节~

这里对于 vue3.x, dom-to-imageprint-js 的使用不做过多的介绍,如果你对这些不熟悉,可以先去了解一下。附上官网地址:

安装依赖

npm install dom-to-image print-js

Hooks 封装

useDomToImage

该方法主要是将 dom 元素转换为图片。并且在原有的库上拓展一些自定义配置项。

types

import type { Options as ReDomToImageOptions } from 'dom-to-image'
import type { BasicTarget, TargetType } from '@/types'

export type ImageType = keyof typeof domToImageMethods

export type DomToImageResult = string | Blob | Uint8ClampedArray | undefined

export interface UseDomToImageOptions extends ReDomToImageOptions {
  /**
   *
   *
   * 指定图片类型,允许传递 imageType 参数,用于指定图片类型
   *
   * @default jpeg
   */
  imageType?: ImageType
  /**
   *
   *
   * 在 dom 转换为图片之前执行
   *
   * @param element current dom
   *
   * @default undefined
   */
  beforeCreate?: <T extends TargetType = Element>(
    element: T | null | undefined,
  ) => void
  /**
   *
   * @param element current dom
   * @param result dom to image result
   *
   * 在 dom 转换为图片之后执行
   *
   * @default undefined
   */
  created?: <T extends TargetType = Element>(
    result: DomToImageResult,
    element: T,
  ) => void
  /**
   *
   * @param error dom to image error
   *
   * 在 dom 转换为图片失败时执行
   *
   * @default undefined
   */
  createdError?: (error?: Error) => void
  /**
   *
   * @param element current dom
   *
   * 无论 dom 转换为图片成功或失败,都会执行
   *
   * @default undefined
   */
  finally?: () => void
}

其中的 BasicTargetTargetType 是一个通用的类型,用于指定 dom 元素的类型。这里就不展开贴出了,点击查看

实现细节

import domToImage from 'dom-to-image'
import { unrefElement } from '@/utils'

const domToImageMethods = {
  svg: domToImage.toSvg,
  png: domToImage.toPng,
  jpeg: domToImage.toJpeg,
  blob: domToImage.toBlob,
  pixelData: domToImage.toPixelData,
}

export const useDomToImage = <T extends HTMLElement>(
  target: BasicTarget<T>,
  options?: UseDomToImageOptions,
) => {
  const {
    beforeCreate,
    created,
    createdError,
    finally: _finally,
    imageType: _imageType,
  } = options ?? {}

  const run = (
    imageType?: UseDomToImageOptions['imageType'],
  ): Promise<DomToImageResult> => {
    return new Promise((resolve, reject) => {
      const element = unrefElement(target)

      beforeCreate?.(element)

      if (!element) {
        createdError?.()

        return reject('useDomToImage: element is undefined.')
      }

      domToImageMethods[imageType ?? _imageType ?? 'jpeg']?.(element, options)
        .then((res) => {
          created?.(res, element)

          return resolve(res)
        })
        .catch((error: Error) => {
          createdError?.(error)

          return reject(error)
        })
        .finally(() => {
          _finally?.()
        })
    })
  }

  return {
    create: run,
  }
}

usePrint

该方法主要是执行打印的相关操作。

types

import type { BasicTarget } from '@/types'

export interface UsePrintOptions
  extends Omit<print.Configuration, 'printable'> {}

export type UsePrintTarget<T = unknown> =
  | BasicTarget
  | string
  | Blob
  | Uint8ClampedArray
  | T[]

实现细节

import print from 'print-js'
import { unrefElement } from '@/utils'

export const usePrint = (target: UsePrintTarget, options?: UsePrintOptions) => {
  const run = () => {
    // 为了兼容 ref 注册的 dom;如果未获取到 dom,则会视为其他的输出方式,交由 print-js 处理
    const _target = unrefElement(target as BasicTarget) || target

    print({
      ...options,
      printable: _target,
    })
  }

  return {
    print: run,
  }
}

printDom 方法

结合刚才封装的 useDomToImageusePrint 方法,实现 dom 元素的打印。

types

import type { UsePrintOptions, UseDomToImageOptions } from '@/hooks'
import type { BasicTarget } from '@/types'

export interface PrintDomOptions {
  printOptions?: Omit<UsePrintOptions, 'printable' | 'type' | 'base64'>
  domToImageOptions?: Omit<UseDomToImageOptions, 'imageType'>
}

实现细节

import { omit } from './basic'
import { useDomToImage, usePrint } from '@/hooks'

export const printDom = <T extends HTMLElement>(
  target: BasicTarget<T>,
  options?: PrintDomOptions,
) => {
  const { domToImageOptions, printOptions } = options ?? {}

  const { create } = useDomToImage(
    target,
    domToImageOptions as UseDomToImageOptions,
  )

  create('jpeg')?.then((res) => {
    const { print } = usePrint(res, {
      type: 'image',
      base64: true,
      targetStyles: ['*'],
      ...omit(printOptions as UsePrintOptions, ['type', 'base64']),
    })

    print()
  })
}

其中 omit 方法实际上就是一个自己封装的过滤方法,可以使用 lodash omit 替代,点击查看

使用

<template>
  <div ref="printDom" class="print-dom">
    <h1>Print Dom</h1>
    <p>Print dom to image and print it.</p>
  </div>
  <button @click="print">Print</button>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { printDom } from '@/utils/print'

const printDomRef = ref<HTMLElement | null>(null)

const print = () => {
  printDom(printDomRef, {})
}
</script>

最后

该方法出自 Ray Template,有兴趣查看完整源码的可以点击查看。

如果觉得对你有帮助,也可以点个 star 支持一下,谢谢!