跳到主要内容

单位与度量

Ydesign 内部使用什么单位?

Ydesign 画布内部统一使用 CSS 像素(px),并遵循 1pt = 1px72 dpi 的默认基准。store 里看到的所有与尺寸有关的数值 —— widthheightfontSizelefttop…… —— 都是像素值

这个选择是为了和 Fabric.js v6 的坐标系一致,也方便和浏览器原生 API(getBoundingClientRectcanvas 宽高等)无缝配合。


切换 UI 显示的单位

虽然底层存的是像素,但你可以通过 store.setUnit 告诉 UI 用什么单位来显示和接收输入(标尺、尺寸面板、打印出血位等都会跟着变):

store.setUnit({
unit: 'mm', // 'px' | 'pt' | 'mm' | 'cm' | 'in'
dpi: 300,
});

切换后:

  • store.unit'mm'
  • store.dpi300
  • 内置标尺会按毫米显示刻度
  • 尺寸面板的输入框会按毫米接收用户输入
  • store.width / store.height 仍然是像素(底层不变,只是 UI 转换了)

📌 所有支持的单位:px(像素)/ pt(磅)/ mm(毫米)/ cm(厘米)/ in(英寸)。当前实现中 ptpx 按 1:1 处理。


在自定义 UI 里显示单位

Ydesign 暴露了一组工具函数处理像素 ↔ 目标单位的换算,所有内置面板用的是同一套,你的自定义组件也应当优先用它们(避免各算各的导致数值对不上):

import {
pxToUnit,
pxToUnitRounded,
pxToUnitString,
unitToPx,
} from '@ydesign/react-editor/utils/unit';

pxToUnit — 像素 → 数值

pxToUnit({ px: 100, unit: 'mm', dpi: 300 });  // => 8.466...
pxToUnit({ px: 300, unit: 'in', dpi: 300 }); // => 1

pxToUnitRounded — 像素 → 四舍五入数值

适合 UI 展示,避免长串小数:

pxToUnitRounded({ px: 100, unit: 'mm', dpi: 300 });              // => 8.47
pxToUnitRounded({ px: 100, unit: 'mm', dpi: 300, precious: 0 }); // => 8

pxToUnitString — 像素 → 带单位后缀字符串

适合直接丢进标签显示:

pxToUnitString({ px: 2480, unit: 'mm', dpi: 300 });  // => '210mm'
pxToUnitString({ px: 300, unit: 'in', dpi: 300 }); // => '1in'
pxToUnitString({ px: 300, unit: 'px', dpi: 72 }); // => '300px'

unitToPx — 数值 → 像素

接收用户输入时用(比如 "A4 宽度 210mm" → 画布要设多少像素):

const widthPx = unitToPx({ unitVal: 210, unit: 'mm', dpi: 300 });
// => 2480.31...

store.setSize({
width: Math.round(widthPx),
height: Math.round(unitToPx({ unitVal: 297, unit: 'mm', dpi: 300 })),
});

一个完整示例:带单位切换的尺寸输入

import { observer } from 'mobx-react-lite';
import { InputNumber, Select } from 'antd';
import { pxToUnitRounded, unitToPx } from '@ydesign/react-editor/utils/unit';

export const SizeInput = observer(({ store }) => {
const widthInCurrentUnit = pxToUnitRounded({
px: store.width,
unit: store.unit as any,
dpi: store.dpi,
precious: 2,
});

const handleChangeWidth = (val: number | null) => {
if (val == null) return;
const px = unitToPx({ unitVal: val, unit: store.unit, dpi: store.dpi });
store.setSize({ width: Math.round(px), height: store.height });
};

return (
<div style={{ display: 'flex', gap: 8 }}>
<InputNumber value={widthInCurrentUnit} onChange={handleChangeWidth} />
<Select
value={store.unit}
onChange={unit => store.setUnit({ unit, dpi: store.dpi })}
style={{ width: 80 }}
options={[
{ value: 'px', label: 'px' },
{ value: 'mm', label: 'mm' },
{ value: 'cm', label: 'cm' },
{ value: 'in', label: 'in' },
]}
/>
</div>
);
});

导出时的 DPI

客户端导出(store.toDataURL / toBlob / saveAsImage

客户端导出不读取 store.dpi,而是通过 multiplier 参数决定输出图的清晰度:

// 1920×1080 画布、multiplier=2 → 输出 3840×2160
await store.saveAsImage({
multiplier: 2,
format: 'png',
});

两种常见放大策略:

你想要的效果建议做法
在网页上展示高清缩略图multiplier: 2
印刷级 300dpi 输出multiplier: 3 ~ 4
保持当前 DPI 按屏幕分辨率放大enableRetinaScaling: true(自动读取 DPR)

云端渲染(/api/render/image

云渲染 APImultiplier 参数行为一致,但最高可到 8×(客户端受浏览器内存限制通常 ≤ 4×)。印刷物料推荐:

{
"json": { /* ... */ },
"format": "png",
"multiplier": 4
}

两种"做高清导出"的思路对比

如果你的业务同时存在 72dpi 屏幕预览 + 300dpi 印刷物料两种需求,有两条路可选:

思路 A:画布固定像素 + 导出用 multiplier(推荐)

  • store.setSize({ width: 2480, height: 3508 })(A4 @ 300dpi 的像素值)
  • 屏幕预览:multiplier: 1
  • 印刷导出:multiplier: 1 + 格式改为 png 即可(画布本身已经是 300dpi 尺寸)

优点:所有元素的像素位置在屏幕和打印上是一致的,不会错位。

思路 B:画布按 72dpi 做 + 导出时靠 multiplier 放大

  • store.setSize({ width: 595, height: 842 })(A4 @ 72dpi)
  • 预览:multiplier: 1
  • 印刷:multiplier: 4(4 × 72 ≈ 288 dpi)

优点:画布小、渲染快、JSON 体积小,适合轻量预览场景。 缺点:细节(小字号、细描边)在放大时可能有像素级偏差。

两种都可行,按产品形态选择即可。


延伸阅读