{"_id":"@4xian/vue-fabric","_rev":"2-dcc9a631039f8314e35f06c9d1675e31","name":"@4xian/vue-fabric","dist-tags":{"latest":"1.1.0"},"versions":{"1.0.0":{"name":"@4xian/vue-fabric","version":"1.0.0","keywords":["fabric","canvas","drawing","vue","paint","graphics","typescript"],"author":{"name":"4xian","email":"vuepro@163.com"},"license":"MPL-2.0","_id":"@4xian/vue-fabric@1.0.0","maintainers":[{"name":"4xian","email":"1145024061@qq.com"}],"homepage":"https://github.com/4xian/vue-fabric#readme","bugs":{"url":"https://github.com/4xian/vue-fabric/issues"},"dist":{"shasum":"864f3e51bbc7c663304c5f13a8c2ea56d15b98d7","tarball":"https://registry.npmjs.org/@4xian/vue-fabric/-/vue-fabric-1.0.0.tgz","fileCount":8,"integrity":"sha512-abTpsn5pKt1ZQfMPiNtdnJv1AdY5nURw+dQGxNtQboHQdWEjPrQ40+iZnagk940yW6l3ku16s/XqfumaVydE7w==","signatures":[{"sig":"MEUCIQD4dqQ1iiktfcwa8OxyWE47j6OgkvCH2ckTOeHfOcb/8QIgKxfIT4LUQRROSle2rEI8hAMs3HJqUoweA170PqLsUdA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":479746},"main":"dist/vue-fabric.umd.js","_from":"file:4xian-vue-fabric-1.0.0.tgz","types":"dist/index.d.ts","module":"dist/vue-fabric.js","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/vue-fabric.js","require":"./dist/vue-fabric.umd.js"},"./style.css":"./dist/style.css"},"scripts":{"dev":"rollup -c rollup.config.mjs --configPlugin typescript -w","demo":"npx serve . -l 3000","lint":"eslint src --ext .ts","test":"vitest","build":"rollup -c rollup.config.mjs --configPlugin typescript","format":"prettier --write \"src/**/*.ts\"","test:ui":"vitest --ui","demo:dev":"concurrently \"pnpm dev\" \"pnpm demo\"","lint:fix":"eslint src --ext .ts --fix","test:run":"vitest run","typecheck":"tsc --noEmit","semantic-release":"semantic-release"},"_npmUser":{"name":"4xian","email":"1145024061@qq.com"},"_resolved":"/tmp/13221b2088483dfea64e3570d103f438/4xian-vue-fabric-1.0.0.tgz","_integrity":"sha512-abTpsn5pKt1ZQfMPiNtdnJv1AdY5nURw+dQGxNtQboHQdWEjPrQ40+iZnagk940yW6l3ku16s/XqfumaVydE7w==","repository":{"url":"git+https://github.com/4xian/vue-fabric.git","type":"git"},"_npmVersion":"10.8.2","description":"基于 fabric.js 的二次封装绘图工具库，支持任意区域画图(直线,曲线)，矩形，文本，图片等功能，不限制框架，原生js可使用。","directories":{},"lint-staged":{"src/**/*.ts":["eslint --fix","prettier --write"]},"_nodeVersion":"20.20.0","_hasShrinkwrap":false,"devDependencies":{"husky":"^9.1.7","jsdom":"^25.0.1","serve":"^14.2.4","tslib":"^2.8.1","eslint":"^9.17.0","rollup":"^4.53.1","vitest":"^4.0.14","prettier":"^3.4.2","@eslint/js":"^9.39.1","@vitest/ui":"^4.0.14","typescript":"^5.7.2","@types/node":"^22.10.1","lint-staged":"^15.2.11","concurrently":"^9.1.0","@commitlint/cli":"^20.0.0","semantic-release":"^25.0.2","rollup-plugin-dts":"^6.1.1","typescript-eslint":"^8.48.0","@vitest/coverage-v8":"4.0.14","@rollup/plugin-terser":"^0.4.4","@semantic-release/git":"^10.0.1","rollup-plugin-postcss":"^4.0.2","eslint-config-prettier":"^10.0.1","@rollup/plugin-commonjs":"^28.0.1","rollup-plugin-svg-import":"^3.0.0","@rollup/plugin-typescript":"^12.1.1","@rollup/plugin-node-resolve":"^16.0.0","@semantic-release/changelog":"^6.0.3","@commitlint/config-conventional":"^20.0.0","rollup-plugin-peer-deps-external":"^2.2.4"},"peerDependencies":{"fabric":"^6.0.0"},"_npmOperationalInternal":{"tmp":"tmp/vue-fabric_1.0.0_1772776050754_0.3403966147271753","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@4xian/vue-fabric","version":"1.1.0","description":"基于 fabric.js 的二次封装绘图工具库，支持任意区域画图(直线,曲线)，矩形，文本，图片等功能，不限制框架，原生js可使用。","main":"dist/vue-fabric.umd.js","module":"dist/vue-fabric.js","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/vue-fabric.js","require":"./dist/vue-fabric.umd.js"},"./style.css":"./dist/style.css"},"repository":{"type":"git","url":"git+https://github.com/4xian/vue-fabric.git"},"keywords":["fabric","canvas","drawing","vue","paint","graphics","typescript"],"author":{"name":"4xian","email":"vuepro@163.com"},"license":"MPL-2.0","peerDependencies":{"fabric":"^6.0.0"},"devDependencies":{"@commitlint/cli":"^20.0.0","@commitlint/config-conventional":"^20.0.0","@eslint/js":"^9.39.1","@rollup/plugin-commonjs":"^28.0.1","@rollup/plugin-node-resolve":"^16.0.0","@rollup/plugin-terser":"^0.4.4","@rollup/plugin-typescript":"^12.1.1","@semantic-release/changelog":"^6.0.3","@semantic-release/git":"^10.0.1","@types/node":"^22.10.1","@vitest/coverage-v8":"4.0.14","@vitest/ui":"^4.0.14","concurrently":"^9.1.0","eslint":"^9.17.0","eslint-config-prettier":"^10.0.1","husky":"^9.1.7","jsdom":"^25.0.1","lint-staged":"^15.2.11","prettier":"^3.4.2","rollup":"^4.53.1","rollup-plugin-dts":"^6.1.1","rollup-plugin-peer-deps-external":"^2.2.4","rollup-plugin-postcss":"^4.0.2","rollup-plugin-svg-import":"^3.0.0","semantic-release":"^25.0.2","serve":"^14.2.4","tslib":"^2.8.1","typescript":"^5.7.2","typescript-eslint":"^8.48.0","vitest":"^4.0.14"},"lint-staged":{"src/**/*.ts":["eslint --fix","prettier --write"]},"engines":{"node":">=18.0.0"},"scripts":{"build":"rollup -c rollup.config.mjs --configPlugin typescript","dev":"rollup -c rollup.config.mjs --configPlugin typescript -w","test":"vitest","test:ui":"vitest --ui","test:run":"vitest run","lint":"eslint src --ext .ts","lint:fix":"eslint src --ext .ts --fix","format":"prettier --write \"src/**/*.ts\"","typecheck":"tsc --noEmit","demo":"npx serve . -l 3000","demo:dev":"concurrently \"pnpm dev\" \"pnpm demo\"","semantic-release":"semantic-release"},"_id":"@4xian/vue-fabric@1.1.0","bugs":{"url":"https://github.com/4xian/vue-fabric/issues"},"homepage":"https://github.com/4xian/vue-fabric#readme","_integrity":"sha512-GRLhMG7GW5dwSbJ6Z1tykaoElV+JO4m2RthXYh3PMhNxJY/mDeo6zC6sGj8bS5UpBiDlGjGMtkX/KLO3U5Ii9g==","_resolved":"/tmp/ad73a8a7a74ba558ba8d85e375c8222a/4xian-vue-fabric-1.1.0.tgz","_from":"file:4xian-vue-fabric-1.1.0.tgz","_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-GRLhMG7GW5dwSbJ6Z1tykaoElV+JO4m2RthXYh3PMhNxJY/mDeo6zC6sGj8bS5UpBiDlGjGMtkX/KLO3U5Ii9g==","shasum":"50c0c11bba85bcc8c33f3879164738a0f2a1f186","tarball":"https://registry.npmjs.org/@4xian/vue-fabric/-/vue-fabric-1.1.0.tgz","fileCount":8,"unpackedSize":578059,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIDG7Lx9ZOR7u3TZN+AJa1Xwm9+y/LEFuHOhEzK2G7YcOAiEApSO5BQH/EkmrVi6ZR9vms4fZLrmwk/8ftAZhRoCq8pk="}]},"_npmUser":{"name":"4xian","email":"1145024061@qq.com"},"directories":{},"maintainers":[{"name":"4xian","email":"1145024061@qq.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/vue-fabric_1.1.0_1780024902679_0.6668711443126174"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-06T05:47:30.629Z","modified":"2026-05-29T03:21:42.926Z","1.0.0":"2026-03-06T05:47:30.992Z","1.1.0":"2026-05-29T03:21:42.825Z"},"bugs":{"url":"https://github.com/4xian/vue-fabric/issues"},"author":{"name":"4xian","email":"vuepro@163.com"},"license":"MPL-2.0","homepage":"https://github.com/4xian/vue-fabric#readme","keywords":["fabric","canvas","drawing","vue","paint","graphics","typescript"],"repository":{"type":"git","url":"git+https://github.com/4xian/vue-fabric.git"},"description":"基于 fabric.js 的二次封装绘图工具库，支持任意区域画图(直线,曲线)，矩形，文本，图片等功能，不限制框架，原生js可使用。","maintainers":[{"name":"4xian","email":"1145024061@qq.com"}],"readme":"# @4xian/vue-fabric\n\n基于 [fabric.js](https://fabricjs.com/) v6 的绘图 SDK，面向标注、测量、区域绘制、文本图片叠加和人员轨迹展示场景。\n\n文档以当前 `src/`、`types/index.d.ts` 和测试为准；如果旧文档与源码不一致，以源码为准。\n\n## 安装\n\n```bash\npnpm add @4xian/vue-fabric fabric\n```\n\n`fabric` 是 `peerDependency`，当前要求 `^6.0.0`。\n\n## 快速开始\n\n```ts\nimport PaintBoard, {\n  SelectTool,\n  DragTool,\n  LineTool,\n  PolylineTool,\n  AreaTool,\n  CurveTool,\n  RectTool,\n  TextTool,\n  ImageTool,\n  Toolbar\n} from '@4xian/vue-fabric'\nimport '@4xian/vue-fabric/style.css'\n\nconst board = new PaintBoard('#canvas-container', {\n  width: 1000,\n  height: 600,\n  backgroundColor: '#fff',\n  zoomOrigin: 'center',\n  zoomAnimationDuration: 1000,\n  enableWheelZoom: false,\n  autoResize: true,\n  pixelRatio: 'auto'\n}).init()\n\nboard\n  .registerTool('select', new SelectTool())\n  .registerTool('drag', new DragTool())\n  .registerTool('line', new LineTool())\n  .registerTool('polyline', new PolylineTool())\n  .registerTool('area', new AreaTool({ enableFill: true }))\n  .registerTool('curve', new CurveTool())\n  .registerTool('rect', new RectTool())\n  .registerTool('text', new TextTool())\n  .registerTool('image', new ImageTool())\n  .setTool('select')\n\nnew Toolbar(board).init()\n\nawait board.setBackgroundImage({\n  source: '/demo/static/draw-bg.png',\n  scaleMode: 'stretch',\n  backgroundVpt: true\n})\n\nboard.on('line:created', data => {\n  console.log(data)\n})\n```\n\n## 当前能力\n\n- 画板核心类：`FabricPaint`，默认导出可自由命名为 `PaintBoard`\n- 工具：`select`、`drag`、`line`、`polyline`、`area`、`curve`、`rect`、`text`、`image`\n- UI：`Toolbar`、`ColorPicker`\n- 编程式文本/图片插入、批量 upsert、按 id 删除\n- JSON / PNG / JPEG / WebP / SVG 导出\n- 背景图、辅助元素显隐、撤销重做\n- `PersonTracker` 人员点位、轨迹、闪烁水波纹\n- 缩放视觉尺寸锁定模式和类型排除配置\n\n## 缩放与自适应\n\n`FabricPaintOptions` 当前与缩放相关的重点配置：\n\n- `zoomStep?: number`\n- `minZoom?: number`\n- `maxZoom?: number`\n- `expandMargin?: number`\n- `expandSize?: number`\n- `zoomOrigin?: 'center' | 'topLeft'`\n- `zoomAnimationDuration?: number`\n- `enableWheelZoom?: boolean`\n- `autoResize?: boolean`\n- `autoResizeMode?: 'canvas' | 'viewport'`\n- `autoResizeFit?: 'contain' | 'cover' | 'stretch'`\n- `referenceSize?: { width: number; height: number }`\n- `pixelRatio?: number | 'auto'`\n- `lockObjectVisualSizeOnZoom?: boolean`\n- `zoomInvariantExcludeTypes?: string[]`\n\n真实语义：\n\n- `zoomIn()`、`zoomOut()`、`setZoom()` 在 `center` 下围绕当前画布内容中心缩放；拖拽平移后不会跳回容器中心。\n- `zoomStep` 默认 `0.2`，当前按“额外比例”计算；默认 `zoomIn()` 是乘 `1.2`，`zoomOut()` 是除 `1.2`。\n- `minZoom` 默认 `0.2`，`maxZoom` 默认 `3`。\n- `expandMargin` 默认 `50`，`expandSize` 默认 `200`；仅在对应自动扩布链路启用时生效。\n- `topLeft` 下缩放会保留当前画布左上锚点。\n- `resetZoom(zoomScale = 1)` 会按当前显示尺寸归位，可传入还原倍率，默认回到业务 zoom `1`。\n- `zoomAnimationDuration` 默认 `500` ms；传 `0` 或负数会关闭动画，回退成同步缩放。\n- `zoomIn()`、`zoomOut()`、`setZoom()`、`resetZoom()`、启用后的滚轮缩放，以及 `resize()` / `autoResize()` 触发的 viewport 归位，最终都复用同一套 viewport transform 落地逻辑。\n- `CanvasManager` 初始化现在直接吃 `FabricPaintOptions` 里的这组缩放配置，不再单独维护第二套缩放配置来源。\n- 动画过程中逐帧触发 `canvas:zooming`，结束时触发 `canvas:zoomed`。\n- `enableWheelZoom` 默认 `false`，显式开启后才会监听 `mouse:wheel`。\n- `autoResize` 默认 `true`。开启后会监听容器尺寸变化，500ms 防抖后重建当前显示尺寸，并保留当前业务 zoom。\n- `autoResizeMode` 默认 `canvas`，保持旧语义：容器变化时同步更新内部基准画布尺寸。\n- `autoResizeMode: 'viewport'` 会固定逻辑参考尺寸，只用 viewport 把参考画布适配到新显示尺寸，适合背景图和对象点位需要保持相对一致的场景。\n- `autoResizeFit` 默认 `stretch`，仅在 `autoResize=true` 且 `autoResizeMode='viewport'` 时生效；`contain` 会完整显示参考画布，`cover` 会铺满并可能裁切，`stretch` 会按 X/Y 独立缩放并可能变形。\n- `referenceSize` 可选；不传时，`autoResizeMode='viewport'` 首次自适应会取当前容器尺寸作为参考尺寸。\n- `pixelRatio` 现在只负责清晰度和 backing store，不再参与业务 zoom，也不会改变 `getZoom()`、逻辑坐标或导出值。\n\n## 补偿缩放模式\n\n当 `lockObjectVisualSizeOnZoom: true` 时：\n\n- 几何坐标继续跟随 viewport 缩放和平移\n- 线宽、辅助点半径、文字字号、图片显示尺寸按 `1 / zoom` 做补偿\n- 导出 JSON 时写出逻辑真值和 `zoomInvariantBase`，不会把当前运行时补偿值当成真实值导出\n\n可通过 `zoomInvariantExcludeTypes` 排除某些类型，让它们继续走 Fabric 正常缩放。常见可选值见 `CustomType`，例如：\n\n```ts\nzoomInvariantExcludeTypes: ['text', 'image']\n```\n\n## 背景图\n\n设置背景图需要手动调用 `setBackgroundImage()`：\n\n```ts\nawait board.setBackgroundImage({\n  source: '/demo/static/draw-bg.png',\n  scaleMode: 'stretch',\n  backgroundVpt: true\n})\n```\n\n注意：\n\n- `FabricPaintOptions.backgroundImage` 类型虽然存在，但当前 `init()` 不会自动应用，仍需手动调用 `setBackgroundImage()`。\n- `setBackgroundImage()` 设置的背景图会被锁定为不可选中、不可拖拽、不可缩放，并始终压到最底层。\n- `scaleMode: 'fill'` 会裁切，`fit` 会留白，`stretch` 会严格铺满画布，`center` 保持原始尺寸居中。\n- `autoResizeMode: 'viewport'` 且 `backgroundVpt: true` 时，`stretch` 会铺满 `referenceSize`，页面尺寸变化只改变 viewport，背景与对象逻辑点位保持同一参考坐标。\n- `repeat` 当前只是按原始尺寸放在左上角，不会真正平铺。\n- `backgroundVpt` 未显式传值时，若开启了补偿缩放，会默认按 `true` 处理。\n\n## 导入导出\n\n```ts\nconst json = board.exportToJSON()\nawait board.importFromJSON(json)\n\nboard.exportToImage({\n  format: 'png',\n  multiplier: 2,\n  download: true\n})\n\nconst svg = board.exportToSVG()\n```\n\n注意：\n\n- `exportToJSON()` 默认排除 `text` 和 `image`；如果要完整导出，传 `excludeTypes: []`。\n- 导入会自动重绑 helper、对象事件、当前 helper 显隐状态，并重新应用当前缩放补偿和背景图展示。\n- `exportToImage()` 导出的是当前视觉画面，`quality` 语义沿用 Fabric，范围按 `0 ~ 1` 使用。\n\n## 辅助元素\n\n当前 helper API 名称沿用历史命名，但作用范围不只 area：\n\n```ts\nboard.showAllAreaHelpers()\nboard.hideAllAreaHelpers()\nboard.toggleAreaHelpers()\nboard.isHelpersVisible()\n```\n\n它们当前会处理：\n\n- `area`\n- `curve`\n- `line`\n- `polyline`\n- `rect`\n\n## PersonTracker\n\n```ts\nconst tracker = board.createPersonTracker({\n  pathType: 'curve',\n  showMovingMarker: true\n})\n\nawait tracker.createMultiplePersons(persons)\nawait tracker.createPersonTraces(id, person, traces)\n```\n\n`PersonTracker` 当前会跟随：\n\n- `canvas:zooming`\n- `canvas:zoomed`\n- `canvas:panned`\n- `canvas:resized`\n\n如果开启补偿缩放：\n\n- 轨迹线宽、marker、文字、图片、水波纹都会按当前 zoom 重新计算显示尺寸\n- 逻辑点位不改，仍按原始业务坐标工作\n\n## UMD\n\n```html\n<link rel=\"stylesheet\" href=\"node_modules/@4xian/vue-fabric/dist/style.css\" />\n<div id=\"canvas-container\"></div>\n\n<script src=\"node_modules/fabric/dist/fabric.js\"></script>\n<script src=\"node_modules/@4xian/vue-fabric/dist/vue-fabric.umd.js\"></script>\n<script>\n  const { FabricPaint, SelectTool, AreaTool, Toolbar } = VueFabric\n\n  const board = new FabricPaint('#canvas-container', {\n    width: 1000,\n    height: 600\n  }).init()\n\n  board\n    .registerTool('select', new SelectTool())\n    .registerTool('area', new AreaTool({ enableFill: true }))\n    .setTool('select')\n\n  new Toolbar(board).init()\n</script>\n```\n\n## 常用 API\n\n```ts\nboard.registerTool(name, tool)\nboard.setTool(name)\n\nboard.setLineColor(color)\nboard.setFillColor(color)\n\nboard.zoomIn()\nboard.zoomOut()\nboard.setZoom(1.5)\nboard.resetZoom()\nboard.resetZoom(0.5)\nboard.setZoomOrigin('center')\n\nboard.resize()\nboard.enableAutoResize()\nboard.disableAutoResize()\n\nboard.exportToJSON()\nboard.importFromJSON(json)\nboard.exportToImage({ format: 'png' })\nboard.exportToSVG()\n\nboard.addText(options)\nboard.insertText(options)\nboard.addImage(options)\nboard.insertImage(options)\n\nboard.batchInsertTexts(list)\nboard.batchInsertImages(list)\nboard.batchRemoveByIds(ids)\n\nboard.showAllAreaHelpers()\nboard.hideAllAreaHelpers()\n\nconst tracker = board.createPersonTracker()\n```\n\n完整 API 见 [docs/API.md](./docs/API.md)，源码职责说明见 [docs/SOURCE.md](./docs/SOURCE.md)。\n\n## 开发\n\n```bash\npnpm dev\npnpm build\npnpm test\npnpm test:run\npnpm lint\npnpm lint:fix\npnpm format\npnpm typecheck\npnpm demo\npnpm demo:dev\n```\n\n## 许可证\n\n[MPL-2.0](./LICENSE)\n","readmeFilename":"README.md"}