背景
在业务开发过程中,会有遇到打印 dom 元素的需求,一般来说的都是采用 html2canvas + print-js实现。
既然是介绍一个全新的方案,那么我们就先来看看 dom-to-image + print-js 的实现方式,并且结合 vue3.x 封装为一个通用 hooks。
前言
为什么会选择 dom-to-image 去替换 html2canvas?其实也是考虑到该库的体积与其优秀的特性,可以更加友好的支持更多 css 特性。具体可以查看官网查阅细节~
这里对于 vue3.x, dom-to-image 和 print-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
}
其中的
BasicTarget和TargetType是一个通用的类型,用于指定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 方法
结合刚才封装的
useDomToImage和usePrint方法,实现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 支持一下,谢谢!