npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gulibs/beautify-qrcode-hooks

v0.0.5

Published

React Hooks,基于 [qr-code-styling](https://www.npmjs.com/package/qr-code-styling),支持单码/批量生成、Canvas 与 SVG 双模式预览、内置背景图与文字叠加插件、PNG/JPEG/WebP/SVG 导出,以及可扩展的插件体系。

Downloads

63

Readme

@gulibs/beautify-qrcode-hooks

React Hooks,基于 qr-code-styling,支持单码/批量生成、Canvas 与 SVG 双模式预览、内置背景图与文字叠加插件、PNG/JPEG/WebP/SVG 导出,以及可扩展的插件体系。


安装

pnpm add @gulibs/beautify-qrcode-hooks qr-code-styling
# 对等依赖:react >=18, react-dom >=18

快速开始

import { useRef, useEffect } from 'react'
import { useBeautifyQRCode } from '@gulibs/beautify-qrcode-hooks'

function QRDemo() {
  const canvasRef = useRef<HTMLCanvasElement>(null)
  const { drawPreview, download } = useBeautifyQRCode({
    content: 'https://example.com',
    dimensions: { width: 300, height: 300 },
  })

  useEffect(() => {
    if (canvasRef.current) drawPreview(canvasRef.current)
  }, [drawPreview])

  return (
    <>
      <canvas ref={canvasRef} style={{ width: 300, height: 300 }} />
      <button onClick={() => download({ name: 'qrcode', extension: 'png' })}>
        下载 PNG
      </button>
    </>
  )
}

<canvas> 的 CSS 宽高(style)控制显示尺寸,canvas.width/height 属性由 Hook 内部自动设置为 CSS 尺寸 × DPR,无需手动赋值。


核心概念

受控 vs 非受控设置

Hook 支持两种状态管理模式:

受控模式(推荐):将外部 settings 对象直接传入,Hook 始终使用该值,无需调用 updateSettings。外部状态变化时直接触发重新渲染。

const settings = useMemo<BeautificationSettings>(() => ({
  qrCodeStyle: { foregroundColor: '#333', margin: 8 },
}), [someState])

const { drawPreview } = useBeautifyQRCode({ content, dimensions, settings })

非受控模式:不传 settings,传入 initialSettings 作为初始值,之后通过 updateSettings 修改内部状态。适合 Hook 内部完全自管理状态的场景。

const { drawPreview, updateSettings } = useBeautifyQRCode({
  content,
  dimensions,
  initialSettings: { qrCodeStyle: { margin: 8 } },
})

// 更新单个字段
updateSettings(prev => ({
  ...prev,
  qrCodeStyle: { ...prev.qrCodeStyle, margin: 16 },
}))

同时传入 settingsinitialSettings/updateSettings 时,settings 优先,updateSettings 仍可写入内部状态但不影响外部受控值。


DPR 与像素一致性

预览(Canvas 模式)

  • drawPreview 自动读取 window.devicePixelRatio,将 canvas buffer 设置为 CSS 尺寸 × DPR(例如 300 px @ DPR=2 → buffer 600×600)。
  • ctx.scale(dpr, dpr) 使所有绘制坐标保持 CSS 像素,Retina 屏幕下自动锐利。
  • Canvas 的 CSS 显示尺寸(style.width/height)由业务侧的 JSX 控制,Hook 不修改。

导出(PNG/JPEG/WebP)

  • getRawDatadownloadexportAsDataURL 均接受 scale 参数(默认 2),以 dimensions × scale 的物理像素分辨率导出,确保下载图与预览清晰度一致。

SVG 模式:矢量格式,任意缩放不失真,DPR 与 scale 对 SVG 导出均无意义。


Canvas 与 SVG 坐标系一致性

两种预览模式共享相同的坐标系:

  • 外层容器尺寸为 dimensions.width × dimensions.height
  • QR 区域qrX, qrY, qrSize)由 computePreviewRect 计算,扣除文字带后居中排列。
  • Canvas 中,QR 图像绘制到 ctx.drawImage(img, qrX, qrY, qrSize, qrSize);SVG 中,QR SVG 元素包裹在 <g transform="translate(qrX, qrY)"> 中。
  • 插件的 svgExtension 收到的 options.width/height外层容器尺寸(不是 qrSize),确保边框、装饰类扩展坐标与 Canvas 插件对齐。

无闪烁渲染

drawPreview 采用双缓冲策略:

  1. 所有异步渲染(生成 QR、加载图片、插件 beforeRender/afterRender)在离屏 canvas 上完成。
  2. 完成后同步执行 visibleCanvas.width = offscreen.width + ctx.drawImage(offscreen, 0, 0),两行代码在同一 JS 任务内,浏览器无法在两者之间插入绘制帧,因此不出现空白闪烁

竞态保护:每次调用 drawPreview/renderPreviewToSvg 都分配单调递增的渲染 ID;若更新的调用在旧调用完成前到达,旧调用在每个异步检查点放弃(不绘制),确保屏幕永远显示最新状态。


useBeautifyQRCode

单码 QR 生成与预览的主 Hook。

入参 UseBeautifyQRCodeOptions

| 属性 | 类型 | 默认 | 说明 | |------|------|------|------| | content | string | — | 受控 QR 内容。传入后 setContent 为 no-op。 | | contentForQR | string | '' | 未传 content 时的默认内容(仅用于初始化内部 state)。 | | settings | BeautificationSettings | — | 受控样式配置。传入后 Hook 直接使用,忽略内部 state 与 initialSettings。 | | initialSettings | BeautificationSettings | {} | 非受控模式的初始样式(仅初始化内部 state 一次)。 | | dimensions | { width: number; height: number } | { width: 300, height: 300 } | 预览与导出的容器尺寸(CSS 像素)。 | | previewMode | 'canvas' \| 'svg' | 'canvas' | 用于消费侧初始化 mode 状态的提示值,不影响 Hook 内部行为。 | | extension | ExtensionFunction | — | 单个 SVG 扩展函数,在所有插件 svgExtension 之后执行。 | | plugins | BeautifyQRCodePlugin[] | — | 静态插件数组,Hook 内部自动构建 registry。 | | pluginRegistry | PluginRegistry \| null | — | 外部 registry(优先于 plugins);适合运行时 register/unregister。 | | onError | QRCodeErrorCallback | — | 预览或导出出错时回调,接收 (error, { source }) 。 |

pluginspluginRegistry 同时传入时,pluginRegistry 生效,plugins 被忽略。


返回值 UseBeautifyQRCodeReturn

状态

| 属性 | 类型 | 说明 | |------|------|------| | settings | BeautificationSettings | 当前生效的完整配置(受控时为传入值,非受控时为内部 state)。 | | updateSettings | (value: BeautificationSettings \| ((prev) => BeautificationSettings)) => void | 更新内部 state(受控模式下内部 state 不影响渲染,但仍可写入;非受控模式的主要更新接口)。 | | content | string | 当前内容(受控时为 content prop,否则为内部 state)。 | | setContent | (value: string \| ((prev) => string)) => void | 非受控场景下更新内容。受控时(传入 content prop)为 no-op。 | | effectiveContent | string | 实际传给 QR 生成器的内容,优先级:content prop > 内部 state > contentForQR。 | | dimensions | { width: number; height: number } | 当前容器尺寸(来自 dimensionsProp 或默认 300×300)。 | | previewMode | 'canvas' \| 'svg' | 与入参 previewMode 一致,固定值。 | | canUseSvgPreview | boolean | 恒为 true,保留用于兼容性。 |

预览

| 方法 | 签名 | 说明 | |------|------|------| | drawPreview | (canvas: HTMLCanvasElement) => Promise<void> | 将 QR 绘制到指定 canvas。自动 DPR 缩放,双缓冲无闪烁,竞态安全。内容为空时立即返回。 | | renderPreviewToSvg | (container: HTMLElement) => Promise<void> | 在指定容器内挂载 SVG 预览(清空后追加)。竞态安全。内容为空时立即返回。 |

导出

| 方法 | 签名 | 说明 | |------|------|------| | getRawData | (extension?: FileExtension, scale?: number) => Promise<Blob \| null> | 获取 Blob。extension 默认 'png'scale 默认 2(SVG 忽略 scale)。内容为空时返回 null。 | | download | (opts?: { name?: string; extension?: FileExtension; scale?: number }) => Promise<void> | 触发浏览器下载。name 默认 'qr'extension 默认 'png'scale 默认 2(SVG 忽略)。 | | exportAsDataURL | (extension?: FileExtension, scale?: number) => Promise<string> | 导出为 Data URL 字符串。extension 默认 'png'scale 默认 2(SVG 忽略)。失败或内容空时返回 ''。 |

scale 说明scale=2 时,300×300 容器导出为 600×600 px 图像。建议 scale 取 2(屏幕)或 3/4(印刷)。SVG 无损矢量,无需 scale。


完整示例

import { useRef, useEffect, useState, useMemo } from 'react'
import { useBeautifyQRCode } from '@gulibs/beautify-qrcode-hooks'
import type { BeautificationSettings, FileExtension } from '@gulibs/beautify-qrcode-hooks'

function QREditor() {
  const canvasRef = useRef<HTMLCanvasElement>(null)
  const svgRef    = useRef<HTMLDivElement>(null)
  const [mode, setMode]       = useState<'canvas' | 'svg'>('canvas')
  const [content, setContent] = useState('https://example.com')
  const [margin, setMargin]   = useState(10)

  // 受控模式:settings 随 margin 变化自动同步
  const settings = useMemo<BeautificationSettings>(() => ({
    qrCodeStyle: {
      foregroundColor: '#1e40af',
      errorCorrectionLevel: 'Q',
      margin,
      dotType: 'extra-rounded',
    },
  }), [margin])

  const { drawPreview, renderPreviewToSvg, download, exportAsDataURL } =
    useBeautifyQRCode({ content, dimensions: { width: 300, height: 300 }, settings })

  useEffect(() => {
    if (mode === 'canvas' && canvasRef.current) drawPreview(canvasRef.current)
  }, [mode, drawPreview])

  useEffect(() => {
    if (mode === 'svg' && svgRef.current) renderPreviewToSvg(svgRef.current)
  }, [mode, renderPreviewToSvg])

  return (
    <div>
      {mode === 'canvas'
        ? <canvas ref={canvasRef} style={{ width: 300, height: 300 }} />
        : <div ref={svgRef} style={{ width: 300, height: 300 }} />}

      <input value={margin} type="range" min={0} max={40}
        onChange={e => setMargin(Number(e.target.value))} />

      <button onClick={() => download({ name: 'qr', extension: 'png', scale: 3 })}>
        下载 3× PNG
      </button>
      <button onClick={() => download({ extension: 'svg' })}>
        下载 SVG
      </button>
    </div>
  )
}

useBatchBeautify

批量生成与导出,每条数据独立配置。

入参 UseBatchBeautifyOptions

| 属性 | 类型 | 默认 | 说明 | |------|------|------|------| | items | BatchBeautifyItem[] | 必填 | 数据项数组,见下方 BatchBeautifyItem 说明。 | | dimensions | { width: number; height: number } | { width: 300, height: 300 } | 每条的预览/导出容器尺寸。 | | format | FileExtension | 'png' | getDataURL / exportAll 的默认格式。 | | extension | ExtensionFunction | — | 所有条共用的 SVG 扩展。 | | plugins | BeautifyQRCodePlugin[] | — | 所有条共用的插件数组。 | | pluginRegistry | PluginRegistry \| null | — | 外部 registry,优先于 plugins。 | | logoMap | Map<number, string> | — | 按条索引指定 logo URL,覆盖 item.initialSettings.logo.url。 | | onError | QRCodeErrorCallback | — | 出错时回调。 |

BatchBeautifyItem

| 属性 | 类型 | 说明 | |------|------|------| | id | string \| number | 唯一标识。 | | content | string | 该条的 QR 内容。 | | title | string? | 可选显示标题。 | | initialSettings | BeautificationSettings? | 该条的样式配置;可被 logoMap 的 logo 覆盖。 |


返回值 UseBatchBeautifyReturn

进度状态

| 属性 | 类型 | 说明 | |------|------|------| | isExporting | boolean | exportAll 执行期间为 true。 | | progress | number | 0–1,exportAll 的完成比例。 | | completedCount | number | exportAll 已完成的条数。 |

方法

| 方法 | 签名 | 说明 | |------|------|------| | getItemPreview | (index: number) => ItemPreviewHandle | 获取指定条(按数组下标)的预览句柄。 | | exportAll | (scale?: number) => Promise<Blob[]> | 批量导出所有条为 Blob 数组;scale 默认 2;格式由 format 决定。返回与 items 等长的数组,失败条为空 Blob。 |

ItemPreviewHandle

| 方法 | 签名 | 说明 | |------|------|------| | drawPreview | (canvas: HTMLCanvasElement) => Promise<void> | 将该条绘制到指定 canvas;双缓冲无闪烁,自动 DPR。 | | getDataURL | (ext?: FileExtension) => Promise<string> | 该条以指定格式(默认 format)导出为 Data URL。 | | renderToSvg | (container: HTMLElement) => Promise<void> | 将该条渲染为 SVG 并挂载到容器;QR 元素通过 position: absolute 定位到正确区域。 |


完整示例

import { useRef, useEffect } from 'react'
import { useBatchBeautify } from '@gulibs/beautify-qrcode-hooks'

const items = [
  { id: 1, content: 'https://a.com', title: 'A' },
  { id: 2, content: 'https://b.com', title: 'B' },
]

function BatchQR() {
  const { getItemPreview, exportAll, isExporting, progress } =
    useBatchBeautify({ items, dimensions: { width: 200, height: 200 } })

  return (
    <div>
      {items.map((item, i) => {
        const canvasRef = useRef<HTMLCanvasElement>(null)
        const handle = getItemPreview(i)
        useEffect(() => {
          if (canvasRef.current) handle.drawPreview(canvasRef.current)
        }, [handle])
        return (
          <div key={item.id}>
            <canvas ref={canvasRef} style={{ width: 200, height: 200 }} />
            <div>{item.title}</div>
          </div>
        )
      })}

      <button onClick={() => exportAll(3)} disabled={isExporting}>
        {isExporting ? `导出中 ${Math.round(progress * 100)}%` : '批量导出 3×'}
      </button>
    </div>
  )
}

computePreviewRect

function computePreviewRect(
  width: number,
  height: number,
  settings?: BeautificationSettings
): PreviewRect

计算 QR 码在容器内的绘制区域,保证 QR 不越界,文字带不覆盖码体。

返回 PreviewRect

| 属性 | 说明 | |------|------| | qrSize | QR 区域的边长(像素),取 min(width, qrAreaHeight)。 | | qrX | QR 区域左上角 X(水平居中:(width - qrSize) / 2)。 | | qrY | QR 区域左上角 Y(扣除 above 文字带高度)。 | | width | 透传容器宽度。 | | height | 透传容器高度。 |

文字带逻辑

  • settings.textOverlaysslot: 'above'content 非空,顶部预留 fontSize + 2×padding + textQrGap 高度,qrY 偏移到该带之后。
  • 若含 slot: 'below'content 非空,底部预留相同高度,qrSize 相应缩小。
  • slot: 'middle'position 定位的项不影响布局。

PluginRegistry

class PluginRegistry {
  register(plugin: BeautifyQRCodePlugin): void
  unregister(pluginId: string): void
  getPlugins(): BeautifyQRCodePlugin[]
  applyTransformOptions(options: QROptions, settings: BeautificationSettings): Options
}

| 方法 | 说明 | |------|------| | register(plugin) | 注册插件;同一 id 重复注册抛 Error。 | | unregister(pluginId) | 按 id 移除插件,不存在时静默。 | | getPlugins() | 返回当前所有已注册插件的数组(按注册顺序)。 | | applyTransformOptions(options, settings) | 依次对每个插件调用 transformOptions,链式传递,返回最终 options。 |

使用时机

  • 需要运行时动态增删插件时用 pluginRegistry
  • 仅使用固定插件列表时,直接传 plugins 即可(Hook 内部自动创建 registry);
  • 两者同时传入时,pluginRegistry 优先,plugins 被忽略。

PreviewArea

<PreviewArea
  ref={ref}
  width={300}
  height={300}
  mode="canvas"      // 'canvas' | 'svg',仅写入 data-preview-mode,不影响行为
  className="..."
  style={{ ... }}
/>

Ref 转发的 <div>,定位为 position: relative; overflow: hidden,作为预览挂载点。不内置绘制逻辑,需业务侧通过 ref 调用 drawPreview(canvas)renderPreviewToSvg(ref.current)


buildStylingOptions

function buildStylingOptions(
  content: string,
  settings: BeautificationSettings,
  width: number,
  height: number,
  type?: 'canvas' | 'svg'   // 默认 'canvas'
): Partial<Options>          // qr-code-styling 的 Options

content + BeautificationSettings + 尺寸转为 qr-code-styling 的原始 Options执行插件 transformOptions

getOptionsForQR 的区别:

| | buildStylingOptions | getOptionsForQR | |---|---|---| | 插件 transformOptions | ❌ 不执行 | ✅ 链式执行 | | 强制 width/height | ❌ 取传入值 | ✅ 强制为 qrSize | | 返回类型 | Partial<Options> | Options |

适用于需要直接操作底层 options自定义渲染流程的场景。


Core API(非 React)

以下函数不依赖 React,可在组件外、Worker、或服务端(仅浏览器 API 可用时)使用。

getOptionsForQR

function getOptionsForQR(
  content: string,
  settings: BeautificationSettings,
  qrSize: number,
  type: 'canvas' | 'svg',
  pluginRegistry?: PluginRegistry | null
): Options

构建完整的 qr-code-styling Options:

  1. 调用 buildStylingOptions 生成基础 options;
  2. 若传入 pluginRegistry,依次执行各插件的 transformOptions
  3. 强制 width = height = qrSize(防止插件修改尺寸导致越界)。

createQRInstance

function createQRInstance(
  content: string,
  settings: BeautificationSettings,
  qrSize: number,
  type: 'canvas' | 'svg',
  extensions: ExtensionFunction[],
  pluginRegistry?: PluginRegistry | null
): QRCodeStyling

创建并返回已应用 extensions(SVG 扩展函数)的 QRCodeStyling 实例,可调用 .append(container) 挂载到 DOM,或 .getRawData(extension) 取 Blob。


getQRRawData

async function getQRRawData(
  content: string,
  settings: BeautificationSettings,
  qrSize: number,
  extension: 'png' | 'jpeg' | 'webp' | 'svg',
  extensions: ExtensionFunction[],
  pluginRegistry?: PluginRegistry | null
): Promise<Blob | null>

直接生成指定格式的 Blob(不经过 Hook 渲染管线,不含插件 beforeRender/afterRender)。适用于服务端批处理、Worker 或不需要 canvas 插件的场景。


getQRRawDataForCanvas

async function getQRRawDataForCanvas(
  content: string,
  settings: BeautificationSettings,
  cssQrSize: number,
  physicalScale: number,
  extensions: ExtensionFunction[],
  pluginRegistry?: PluginRegistry | null
): Promise<Blob | null>

专为高 DPR Canvas 渲染设计的 PNG 生成器,解决 getQRRawData 直接放大时像素比例失真的问题:

  1. 先在 CSS 像素尺寸cssQrSize)下解析所有 options(含插件 transformOptions),确保插件始终看到与 SVG 模式一致的 CSS 像素值;
  2. 再将像素型字段marginimageOptions.margin)按 physicalScale 等比放大,width/height 设为 cssQrSize × physicalScale
  3. 比例型字段(imageSizebackgroundRound 等)保持不变。

physicalScale <= 1 时退化为 getQRRawData


downloadURI

function downloadURI(uri: string, name: string): void

通过创建隐藏 <a download> 触发浏览器下载。uri 可为 Data URL 或 Blob URL;name 为文件名(含扩展名)。


内置插件

backgroundImagePlugin

在 QR 码下方渲染自定义背景图。

配置:通过 settings.pluginOptions['background-image'] 传入 BackgroundImageOptions

| 属性 | 类型 | 默认 | 说明 | |------|------|------|------| | imageUrl | string | — | Data URL 或 Blob URL(必须是同域或 Data URL) | | opacity | number | 1 | 不透明度 0–1 | | position | 'fill' \| 'fit' \| 'stretch' | 'fill' | fill=裁切填满;fit=等比缩放居中留空;stretch=拉伸不保比 |

工作机制

  • transformOptions:当 imageUrl 存在时,自动将 QR 背景色设为 transparent
  • beforeRender(Canvas):异步加载图片(带内存缓存),在绘制 QR 前将背景图 draw 到 canvas,自动处理 DPR 坐标(从 canvas.dataset.cssWidth/Height 读取 CSS 尺寸)。
  • svgExtension(SVG):在 SVG 根节点最前面插入 <image> 元素,preserveAspectRatioposition 映射为对应值。
import { useBeautifyQRCode, backgroundImagePlugin } from '@gulibs/beautify-qrcode-hooks'
import type { BackgroundImageOptions, BeautificationSettings } from '@gulibs/beautify-qrcode-hooks'

const settings = useMemo<BeautificationSettings>(() => ({
  qrCodeStyle: { backgroundColor: 'transparent', margin: 8 },
  pluginOptions: {
    'background-image': {
      imageUrl: myBgUrl,
      opacity: 0.7,
      position: 'fill',
    } satisfies BackgroundImageOptions,
  },
}), [myBgUrl])

const { drawPreview } = useBeautifyQRCode({
  content: 'https://example.com',
  dimensions: { width: 300, height: 300 },
  plugins: [backgroundImagePlugin],
  settings,
})

loadBackgroundImage(url):独立导出的工具函数,返回 Promise<HTMLImageElement>,带 URL→HTMLImageElement 内存缓存,可用于预加载。


textOverlayPlugin

在 QR 码上方/下方/指定位置渲染文字,Canvas 与 SVG 双模式同步。

配置:通过 settings.textOverlays: TextOverlayItem[] 传入,无需 pluginOptions

TextOverlayItem

| 属性 | 类型 | 默认 | 说明 | |------|------|------|------| | id | string | 必填 | 唯一标识。 | | content | string | 必填 | 文字内容,空字符串不渲染。 | | slot | 'above' \| 'middle' \| 'below' | — | 预设槽位。above/below 配合 computePreviewRect 预留空间;middle 居中于 QR 区域。slot 优先于 position。 | | position | { x: number; y: number } | { x: 0.5, y: 0.9 } | 归一化坐标(0=左/上,1=右/下),无 slot 时生效,映射为 x × width, y × height。 | | positionOffset | { x: number; y: number } | — | 在 slot/position 基础上的附加归一化偏移(目前保留字段,插件内部暂未应用)。 | | style | TextOverlayStyle | — | 文字样式,见下表。 |

TextOverlayStyle

| 属性 | 类型 | 默认 | 说明 | |------|------|------|------| | fontSize | number | 16 | 字号(px) | | fontFamily | string | 'Arial' | 字体族 | | color | string | '#000000' | 文字颜色 | | fontWeight | string | 'normal' | 字重,如 'bold''600' | | alignment | 'left' \| 'center' \| 'right' | 'center' | 水平对齐 | | strokeWidth | number | — | 描边宽度(px);设置后同时绘制描边 | | strokeColor | string | '#ffffff' | 描边颜色 | | shadowOffsetX | number | 0 | 阴影 X 偏移 | | shadowOffsetY | number | 0 | 阴影 Y 偏移 | | shadowBlur | number | — | 阴影模糊半径;> 0 时启用阴影 | | shadowColor | string | 'rgba(0,0,0,0.5)' | 阴影颜色 | | backgroundColor | string | — | 文字背景填充色(绘制在文字矩形后面) | | borderWidth | number | — | 文字背景边框宽度;需同时设置 backgroundColorborderColor | | borderColor | string | '#000000' | 文字背景边框颜色 |

工作机制

  • afterRender(Canvas):遍历 settings.textOverlays,按 slot/position 计算坐标,逐条绘制文字(含背景、描边、阴影)。从 canvas.dataset.cssWidth/Height 读取 CSS 尺寸,DPR 无关。
  • svgExtension(SVG):为每条文字生成对应的 SVG <text> 元素,样式与 Canvas 渲染保持视觉一致。
import { useBeautifyQRCode, textOverlayPlugin } from '@gulibs/beautify-qrcode-hooks'
import type { BeautificationSettings } from '@gulibs/beautify-qrcode-hooks'

const settings = useMemo<BeautificationSettings>(() => ({
  qrCodeStyle: { margin: 8 },
  textOverlays: [
    {
      id: 'header',
      content: '扫码关注',
      slot: 'above',
      style: { fontSize: 16, color: '#1e40af', fontWeight: 'bold' },
    },
    {
      id: 'footer',
      content: 'example.com',
      slot: 'below',
      style: { fontSize: 13, color: '#6b7280' },
    },
  ],
}), [])

const { drawPreview, renderPreviewToSvg } = useBeautifyQRCode({
  content: 'https://example.com',
  dimensions: { width: 300, height: 360 },  // height > width 为文字带预留空间
  plugins: [textOverlayPlugin],
  settings,
})

组合多个插件

多个插件按数组顺序执行,彼此独立:

const { drawPreview } = useBeautifyQRCode({
  content: 'https://example.com',
  dimensions: { width: 300, height: 360 },
  plugins: [backgroundImagePlugin, textOverlayPlugin],
  settings: useMemo(() => ({
    qrCodeStyle: { backgroundColor: 'transparent', margin: 8 },
    pluginOptions: {
      'background-image': { imageUrl: bgUrl, opacity: 0.6, position: 'fill' },
    },
    textOverlays: [
      { id: 'footer', content: 'Scan Me', slot: 'below', style: { fontSize: 14 } },
    ],
  }), [bgUrl]),
})

插件开发

插件只负责渲染逻辑(修改 QR options、修改 SVG、在 canvas 上绘制);UI 与状态由业务侧管理,通过 settings.pluginOptions[id] 与插件通信。

BeautifyQRCodePlugin 接口

interface BeautifyQRCodePlugin {
  id: string
  transformOptions?: (options: QROptions, settings: BeautificationSettings) => QROptions
  svgExtension?: ExtensionFunction | PluginSvgExtension
  svgExtensionOnlyForSvgOutput?: boolean
  beforeRender?: (
    ctx: CanvasRenderingContext2D,
    settings: BeautificationSettings,
    canvas: HTMLCanvasElement
  ) => void | Promise<void>
  afterRender?: (
    ctx: CanvasRenderingContext2D,
    settings: BeautificationSettings,
    canvas: HTMLCanvasElement
  ) => void | Promise<void>
}

| 字段 | 说明 | |------|------| | id | 唯一字符串,作为 settings.pluginOptions[id] 的键,以及 PluginRegistry 的索引。 | | transformOptions(options, settings) | 在生成 QR 前修改底层 qr-code-styling Options(如增加 margin、修改颜色)。链式执行,必须返回新 options 对象(不可 mutate)。不论 Canvas 还是 SVG 模式均会调用。 | | svgExtension(svg, options[, settings]) | 仅在有 SVG 输出时调用(SVG 预览、SVG 导出;以及 svgExtensionOnlyForSvgOutputfalse 时的栅格导出)。操作 SVG 根节点,追加/修改 SVG 元素。options.width/height外层容器尺寸(见坐标系一致性章节)。 | | svgExtensionOnlyForSvgOutput | 默认 false。设为 true 后,svgExtension 仅在 SVG 输出时调用,栅格(PNG/JPEG/WebP)导出时跳过。通常与同时实现 afterRender 的插件配合,避免同一效果被渲染两次。 | | beforeRender(ctx, settings, canvas) | Canvas 绘制前调用(在生成 QR 之前)。支持 async,Hook 会 await。典型用途:绘制背景图、渐变背景。 | | afterRender(ctx, settings, canvas) | Canvas 绘制后调用(在 QR 绘制到 canvas 之后)。支持 async,Hook 会 await。典型用途:绘制文字、水印、边框装饰。 |

执行顺序

Canvas 预览 / 栅格导出(PNG, JPEG, WebP):
  transformOptions(若用 registry)
  → beforeRender × N(按 plugins 数组顺序,await async)
  → 生成 QR 图像(getQRRawDataForCanvas)
  → 将 QR drawImage 到 canvas
  → afterRender × N(按 plugins 数组顺序,await async)

SVG 预览 / SVG 导出:
  transformOptions(若用 registry)
  → 生成 QR SVG blob
  → svgExtension × N(按 plugins 数组顺序;skipped if svgExtensionOnlyForSvgOutput && 当前是栅格路径)

Canvas DPR 注意事项

beforeRender / afterRender,canvas buffer 尺寸为物理像素(CSS 尺寸 × DPR),但 ctxscale(dpr, dpr),所以绘制坐标使用 CSS 像素即可:

// ✅ 正确:从 dataset 读取 CSS 尺寸
const w = Number(canvas.dataset.cssWidth)  || canvas.width
const h = Number(canvas.dataset.cssHeight) || canvas.height
ctx.fillText('hello', w / 2, h - 10)  // CSS 像素坐标

// ❌ 错误:直接用 canvas.width 是物理像素,DPR=2 时坐标翻倍偏移
ctx.fillText('hello', canvas.width / 2, canvas.height - 10)

插件状态读写

// 业务侧(组件)写入状态
updateSettings(prev => ({
  ...prev,
  pluginOptions: {
    ...prev.pluginOptions,
    [myPluginId]: { text: '© 2026', color: '#999' },
  },
}))

// 插件内读取状态
interface MyOptions { text?: string; color?: string }

const plugin: BeautifyQRCodePlugin = {
  id: myPluginId,
  afterRender(ctx, settings, canvas) {
    const opts = settings.pluginOptions?.[myPluginId] as MyOptions | undefined
    if (!opts?.text) return
    // ...绘制
  },
}

自定义插件完整示例

import type { BeautifyQRCodePlugin } from '@gulibs/beautify-qrcode-hooks'

interface WatermarkOptions {
  text?: string
  color?: string
  fontSize?: number
}

/**
 * 水印插件:在 canvas 右下角 afterRender 绘制,SVG 模式通过 svgExtension 添加同等效果。
 * svgExtensionOnlyForSvgOutput: true — 避免 PNG 导出时 svgExtension 与 afterRender 重复绘制。
 */
export const watermarkPlugin: BeautifyQRCodePlugin = {
  id: 'watermark',
  svgExtensionOnlyForSvgOutput: true,

  afterRender(ctx, settings, canvas) {
    const opts = settings.pluginOptions?.['watermark'] as WatermarkOptions | undefined
    if (!opts?.text) return
    const w = Number(canvas.dataset.cssWidth)  || canvas.width
    const h = Number(canvas.dataset.cssHeight) || canvas.height
    const fs = opts.fontSize ?? 12
    ctx.save()
    ctx.font = `${fs}px sans-serif`
    ctx.fillStyle = opts.color ?? 'rgba(0,0,0,0.25)'
    ctx.textAlign = 'right'
    ctx.textBaseline = 'bottom'
    ctx.fillText(opts.text, w - 6, h - 6)
    ctx.restore()
  },

  svgExtension(svg, _options, settings) {
    const opts = settings?.pluginOptions?.['watermark'] as WatermarkOptions | undefined
    if (!opts?.text) return
    const w = Number(svg.getAttribute('width'))  || 300
    const h = Number(svg.getAttribute('height')) || 300
    const fs = opts.fontSize ?? 12
    const ns = 'http://www.w3.org/2000/svg'
    const el = document.createElementNS(ns, 'text')
    el.setAttribute('x', String(w - 6))
    el.setAttribute('y', String(h - 6))
    el.setAttribute('text-anchor', 'end')
    el.setAttribute('dominant-baseline', 'auto')
    el.setAttribute('font-size', String(fs))
    el.setAttribute('font-family', 'sans-serif')
    el.setAttribute('fill', opts.color ?? 'rgba(0,0,0,0.25)')
    el.textContent = opts.text
    svg.appendChild(el)
  },
}

// 使用方式:
// plugins={[watermarkPlugin]}
// settings={{ pluginOptions: { watermark: { text: '© 2026', color: '#888' } } }}

Extension vs Plugin vs PluginRegistry 对比

| | extension | plugins | pluginRegistry | |---|---|---|---| | 传参位置 | extension: fn | plugins: [...] | pluginRegistry: registry | | 支持 transformOptions | ❌ | ✅ | ✅ | | 支持 svgExtension | ✅(即为 extension 本身) | ✅ | ✅ | | 支持 beforeRender / afterRender | ❌ | ✅ | ✅ | | 支持运行时增删 | ❌ | ❌(重传数组) | ✅ register/unregister | | 接收 settings 参数 | ❌ | ✅ | ✅ | | 适用场景 | 简单 SVG 装饰(边框、水印),无状态 | 有配置的功能插件,静态列表 | 动态插件管理,运行时按需启用 |


BeautificationSettings 完整类型参考

BeautificationSettings

interface BeautificationSettings {
  qrCodeStyle?: QRCodeStyleSettings
  logo?: LogoSettings
  textOverlays?: TextOverlayItem[]
  pluginOptions?: Record<string, unknown>  // 插件状态,按 plugin.id 键
}

QRCodeStyleSettings

| 属性 | 类型 | 默认 | 说明 | |------|------|------|------| | foregroundColor | string | '#000000' | 单色模式下码点颜色(dotsColorType: 'single' 时生效) | | backgroundColor | string | '#FFFFFF' | 单色背景色(backgroundColorType: 'single' 时生效) | | errorCorrectionLevel | 'L' \| 'M' \| 'Q' \| 'H' | 'H' | 纠错级别;有 logo 时建议 'Q''H' | | margin | number | 0 | 外边距(px,CSS 尺寸空间),QR 图内部的静区宽度 | | version | number | 自动 | QR 版本 1–40(点阵密度);若内容需要更高版本则自动升级 | | mode | 'Numeric' \| 'Alphanumeric' \| 'Byte' \| 'Kanji' | 自动 | 编码模式;内容不符合该模式时自动降级 | | dotType | DotType | 'square' | 码点形状:square dots rounded classy classy-rounded extra-rounded | | cornerSquareType | CornerSquareType | 'square' | 角框(定位图案外框)形状:square dot extra-rounded rounded dots classy classy-rounded | | cornerDotType | CornerDotType | 'square' | 角点(定位图案内点)形状:square dot rounded dots classy classy-rounded extra-rounded | | shape | 'square' \| 'circle' | 'square' | QR 整体外形(circle 将码裁切为圆形) | | dotsRoundSize | boolean | true | 是否对码点启用圆角对齐优化 | | backgroundRound | number | — | 背景矩形圆角半径(0–1 比例) | | dotsColorType | 'single' \| 'gradient' | 'single' | 码点着色方式;'gradient' 时读 foregroundGradient | | foregroundGradient | ColorGradient | — | 码点渐变配置(dotsColorType: 'gradient' 时生效) | | cornerSquareColorType | 'single' \| 'gradient' | 'single' | 角框着色方式 | | cornerSquareColor | string | 同 foregroundColor | 角框单色颜色 | | cornerSquareGradient | ColorGradient | — | 角框渐变 | | cornerDotColorType | 'single' \| 'gradient' | 'single' | 角点着色方式 | | cornerDotColor | string | 同 foregroundColor | 角点单色颜色 | | cornerDotGradient | ColorGradient | — | 角点渐变 | | backgroundColorType | 'single' \| 'gradient' | 'single' | 背景着色方式 | | backgroundGradient | ColorGradient | — | 背景渐变 |

ColorGradient

interface ColorGradient {
  type: 'linear' | 'radial'
  rotation?: number                                // 旋转角度(度,非弧度)
  colorStops: Array<{ offset: number; color: string }>  // offset 0–1
}

LogoSettings

| 属性 | 类型 | 默认 | 说明 | |------|------|------|------| | url | string | 必填 | logo 图片 URL;支持 Data URL(data:)和 HTTP URL | | size | number | 40 | logo 占 QR 尺寸的百分比(1–100) | | margin | number | 0 | logo 周围的清除区域宽度(px,输出图像空间) | | hideBackgroundDots | boolean | true | 是否隐藏 logo 区域内的码点 | | crossOrigin | 'anonymous' \| 'use-credentials' | 自动 | 跨域图片的 CORS 策略;HTTP URL 自动设为 'anonymous' | | saveAsBlob | boolean | 自动 | Data URL 图片是否先转为 Blob 再加载(Data URL 时自动为 true) |


其他类型

| 类型 | 说明 | |------|------| | FileExtension | 'png' \| 'jpeg' \| 'webp' \| 'svg' | | RasterExportFormat | 'png' \| 'jpeg' \| 'webp'(不含 svg) | | ExtensionFunction | (svg: SVGElement, options: QROptions) => void | | PluginSvgExtension | (svg: SVGElement, options: QROptions, settings?: BeautificationSettings) => void | | PreviewRect | { qrSize, qrX, qrY, width, height } | | QRCodeErrorContext | { source: 'canvasPreview' \| 'svgPreview' \| 'export' \| 'batch' } | | QRCodeErrorCallback | (error: unknown, context: QRCodeErrorContext) => void | | QRCodeStylingOptions | qr-code-styling 的原始 Options 类型(re-export) | | BatchDisplayMode | 'grid' \| 'tabs'(保留,未在 Hook 内使用) |


完整导出列表

// Hooks
export { useBeautifyQRCode } from '...'
export type { UseBeautifyQRCodeOptions, UseBeautifyQRCodeReturn } from '...'

export { useBatchBeautify } from '...'
export type { UseBatchBeautifyOptions, UseBatchBeautifyReturn, ItemPreviewHandle } from '...'

// 工具函数
export { computePreviewRect } from '...'
export { PluginRegistry } from '...'
export { buildStylingOptions } from '...'
export { PreviewArea } from '...'
export type { PreviewAreaProps } from '...'

// 内置插件
export { backgroundImagePlugin, loadBackgroundImage } from '...'
export { textOverlayPlugin } from '...'

// Core(非 React)
export { getOptionsForQR, createQRInstance, getQRRawData, downloadURI } from '...'

// 类型
export type {
  BeautificationSettings, QRCodeStyleSettings, LogoSettings,
  TextOverlayItem, TextOverlayStyle, BackgroundImageOptions,
  BeautifyQRCodePlugin, ExtensionFunction, PluginSvgExtension,
  BatchBeautifyItem, BatchDisplayMode, FileExtension, RasterExportFormat,
  PreviewRect, QRCodeErrorCallback, QRCodeErrorContext,
  QRCodeStylingOptions, ColorGradient, ColorGradientType,
  DotType, CornerSquareType, CornerDotType,
} from '...'

与 qr-code-styling 官网能力对照

| 区域 | 选项 | 支持 | 映射字段 | |------|------|------|------| | 主选项 | Data | ✅ | content | | | Width / Height | ✅ | dimensions | | | Margin(静区) | ✅ | qrCodeStyle.margin | | | Image(Logo) | ✅ | logo.url | | Dots | 样式 | ✅ | qrCodeStyle.dotType | | | 单色 | ✅ | qrCodeStyle.foregroundColor | | | 渐变(含旋转) | ✅ | qrCodeStyle.foregroundGradient | | Corners Square | 样式 | ✅ | qrCodeStyle.cornerSquareType | | | 单色/渐变 | ✅ | qrCodeStyle.cornerSquareColor/Gradient | | Corners Dot | 样式 | ✅ | qrCodeStyle.cornerDotType | | | 单色/渐变 | ✅ | qrCodeStyle.cornerDotColor/Gradient | | Background | 单色/渐变 | ✅ | qrCodeStyle.backgroundColor/Gradient | | | 圆角 | ✅ | qrCodeStyle.backgroundRound | | Background Image | 贴图 / 透明度 / 填充模式 | ✅ | backgroundImagePlugin + BackgroundImageOptions | | Text Overlay | 自由位置/上下文字 | ✅ | textOverlayPlugin + TextOverlayItem[] | | Image Options | Hide background dots | ✅ | logo.hideBackgroundDots | | | Logo Size | ✅ | logo.size(百分比) | | | Logo Margin | ✅ | logo.margin | | QR Options | Type Number(版本) | ✅ | qrCodeStyle.version | | | Error Correction Level | ✅ | qrCodeStyle.errorCorrectionLevel | | | Mode | ✅ | qrCodeStyle.mode | | | Shape(圆形 QR) | ✅ | qrCodeStyle.shape | | Export | PNG / JPEG / WebP / SVG | ✅ | download / getRawData | | | 自定义导出分辨率 | ✅ | scale 参数(默认 2×) | | | 导出为 Data URL | ✅ | exportAsDataURL | | | 配置序列化为 JSON | ✅ | JSON.stringify(settings) |


脚本

pnpm dev    # 启动示例应用(Vite dev server,含完整功能演示)
pnpm build  # 构建库到 dist/(ES + CJS + 类型声明)
pnpm lint   # ESLint 检查