在项目开发中我们经常会用到图片合成这个功能,最近也是闲着没事儿干将这个功能封装了一下,只需要传递一个图片路径和二维码配置便可得到一张海报图,使用示例如下
// 生成海报图片
const posterImgPath = await generatePoster({
// 海报背景图片
posterBgImgPath: '/img/vue.jpg',
// 二维码选项配置
qrCodeOptions: {
width: 140,
height: 140,
// 二维码的纠错级别
correctLevel: 0
}
})
是不是很简单方便,完全符合海报图片合成较多的场景,如果你想使用该函数的话,那么请先安装以下包
// 安装 合成图片 和 生成二维码 的依赖包
pnpm i html2canvas qrcodejs2-fix
// 安装ts类型工具包
pnpm i @dyb-dev/ts-config -D
安装完成后还需要在tsconfig.json文件中的compilerOptions.types选项添加 @dyb-dev/ts-config/types 声明
示例代码如下:
"compilerOptions": {
"types": ["@dyb-dev/ts-config/types"]
}
前置步骤完成后就可放心使用以下核心代码了:
import html2canvas from 'html2canvas'
// @ts-ignore
import QRCode from 'qrcodejs2-fix'
import type { Options as IHtml2canvasOptions } from 'html2canvas'
/** TPositionItem 类型表示位置,可以是数字或空值 */
type TPositionItem = number | null
/** IQrCodeOptions 接口表示 QRCode 配置选项 */
interface IQrCodeOptions {
/** QRCode 链接数据 */
text: string
/** QRCode 图片宽度 */
width?: number
/** QRCode 图片高度 */
height?: number
/** QRCode 的深色部分颜色 */
colorDark?: string
/** QRCode 的浅色部分颜色 */
colorLight?: string
/** QRCode 的纠错级别 0 | 1 | 2 | 3 默认: 2 */
correctLevel?: RangeOfNumbers<0, 3>
/** 二维码位置信息 top,right,bottom,left 默认:[null,null,0,0] */
position?: GenerateTuple<TPositionItem, 4>
/** 二维码边框颜色 */
borderColor?: string
/** 二维码边框宽度,单位 px */
borderWidth?: number
/** 二维码圆角,单位 px */
borderRadius?: number
}
/** 创建海报选项基础类型 */
interface IGeneratePosterOptionsBase {
/** 海报盒子 DOM 元素 */
posterBox: HTMLElement
/** 海报底图绝对路径 */
posterBgImgPath: string
/** 生成海报的图片类型 默认: png */
posterImgType?: 'jpg' | 'jpeg' | 'png' | 'webp' | 'bmp' | 'gif'
/** QRCode 配置选项 */
qrCodeOptions: IQrCodeOptions
/** html2canvas 配置选项(可选) */
html2canvasOptions?: IHtml2canvasOptions
}
/** 生成海报的联合类型 */
type TGeneratePosterOptions =
| ModifyProperties<IGeneratePosterOptionsBase, 'posterBox'>
| ModifyProperties<IGeneratePosterOptionsBase, 'posterBgImgPath'>
/** 重载类型 */
interface IGeneratePosterFn {
(
options: ModifyProperties<IGeneratePosterOptionsBase, 'posterBox'>
): Promise<string | void>
(
options: ModifyProperties<IGeneratePosterOptionsBase, 'posterBgImgPath'>
): Promise<string | void>
}
// 生成海报图片
const generatePoster: IGeneratePosterFn = async(
options: TGeneratePosterOptions
): Promise<string | void> => {
try {
const {
posterBox,
posterBgImgPath,
posterImgType = 'png',
qrCodeOptions,
html2canvasOptions = {}
} = options
const {
position = [null, 0, 0, null],
borderColor = '#ffffff',
borderWidth = 10,
borderRadius = 0
} = qrCodeOptions
/** 二维码占位图 div */
const _qrCodeBox = document.createElement('div')
_qrCodeBox.style.position = 'absolute'
_qrCodeBox.style.boxSizing = 'border-box'
_qrCodeBox.style.backgroundColor = borderColor
_qrCodeBox.style.padding = `${borderWidth}px`
_qrCodeBox.style.borderRadius = `${borderRadius}px`
_qrCodeBox.style.inset = position
.map((item) => typeof item === 'number' ? `${item}px` : 'auto')
.join(' ')
// 为了解决二维码图片(行内元素)与外部div(块元素)有间隔,导致二维码下边框高度与其他不一致的问题,设置字体大小为0
_qrCodeBox.style.fontSize = '0'
/** 默认的 QRCode 配置 */
const _defaultQrCodeOptions: Partial<IQrCodeOptions> = {
correctLevel: 2
}
// 生成二维码
new QRCode(_qrCodeBox, {
..._defaultQrCodeOptions,
...qrCodeOptions
})
/** 海报盒子 */
let _posterBox = posterBox
// 如果未传入海报盒子,则创建一个
if (!_posterBox) {
_posterBox = document.createElement('div')
_posterBox.style.position = 'absolute'
_posterBox.style.top = '-9999px'
_posterBox.style.left = '-9999px'
}
/** 海报底图 img */
let _posterBgImg: HTMLImageElement | null = null
// 如果传入了海报底图路径,则创建一个 img 元素
if (posterBgImgPath) {
_posterBgImg = document.createElement('img')
_posterBgImg.src = posterBgImgPath
_posterBgImg.style.display = 'block'
_posterBox.appendChild(_posterBgImg)
}
_posterBox.appendChild(_qrCodeBox)
document.body.appendChild(_posterBox)
// 默认的 html2canvas 配置
const _defaultHtml2canvasOptions: Partial<IHtml2canvasOptions> = {
backgroundColor: null, // 生成出来的图片有白色边框 所以设置为 null
allowTaint: true, // 允许跨域图像使用
useCORS: true // 是否使用 CORS(跨域资源共享)头来加载跨域图像
}
// 使用 html2canvas 生成海报图
const _canvas = await html2canvas(_posterBox, {
..._defaultHtml2canvasOptions,
...html2canvasOptions
})
// 将 canvas 转换为 data URL
const _dataURL = _canvas.toDataURL(`image/${posterImgType}`)
// 如果传入了海报盒子,则手动移除添加的子元素
if (posterBox) {
_posterBox.removeChild(_qrCodeBox)
_posterBgImg && _posterBox.removeChild(_posterBgImg)
} else {
_posterBox.remove()
}
return _dataURL
} catch (error) {
console.error('generatePoster() error =>>', error)
throw error
}
}
希望能够帮助到大家!