跳转到内容

底层 Library

应用需要完全控制布局、控件、Dialog、多语言和视觉设计时,可以直接使用底层 Library

createCustomizer() 返回稳定的 ProductCustomizerApi 能力契约。它不会生成工具栏、面板、Dialog 或状态界面,也不会暴露 Fabric.js 或 Three.js 实例

挂载两个区域

编辑器和查看器需要使用两个不同并且尺寸稳定的 DOM 元素

html
<div class="customizer-layout">
  <div id="texture-editor"></div>
  <div id="product-viewer"></div>
</div>
css
.customizer-layout {
  display: grid;
  grid-template-columns: 1fr 1fr;
  min-height: 640px;
}

#texture-editor,
#product-viewer {
  min-width: 0;
  min-height: 0;
}

创建实例

ts
import { createCustomizer } from 'customforge'
import 'customforge/style.css'

const customizer = await createCustomizer({
  editor: '#texture-editor',
  viewer: '#product-viewer',
  editorWidth: 1024,
  editorHeight: 512,
  historyLimit: 50,
  product: {
    modelUrl: 'https://cdn.example.com/product.glb',
    textureUrl: 'https://cdn.example.com/base-texture.png',
    surfaceMesh: 'PrintArea',
    textureFlipY: false,
  },
  appearance: {
    editor: {
      selectionBorder: '#0057b8',
      controlBorder: '#0057b8',
      controlSize: 7,
      uvBoundary: '#d92d20',
    },
    viewer: { backgroundColor: '#f4f4f5' },
  },
})

省略 product 时会使用内置演示杯子

编辑器宽高是逻辑纹理尺寸,重新加载已保存设计时必须保持一致

添加对象

ts
const text = customizer.addText({
  text: 'Hello world',
  name: '主标题',
  x: 120,
  y: 180,
  width: 480,
  fontFamily: 'Arial',
  fontSize: 64,
  color: '#172126',
})

const image = await customizer.addImage({
  src: 'https://cdn.example.com/logo.png',
  name: 'Logo',
  x: 720,
  y: 240,
  width: 220,
})

两个方法都会返回带稳定 ID 的可序列化对象快照,自定义界面可以立即更新自己的选区或图层状态

文字位置使用初始左上角,图片位置使用图片中心点

对象会被自动缩放或移动,确保完整保留在编辑画布内

省略 fontSize 时,新文字使用 22pxAddTextOptions 还支持 fontWeightfontStyleunderlinetextAlignlineHeightcharSpacingbackgroundColor

修改已有文字

使用稳定对象 ID 修改文字内容或排版,不需要直接访问 Fabric.js

ts
const [textId] = customizer.getSelectedObjectIds()

if (textId) {
  customizer.updateText(textId, {
    fontSize: 28,
    fontWeight: 'bold',
    fontStyle: 'italic',
    underline: true,
    textAlign: 'center',
    lineHeight: 1.2,
    charSpacing: 40,
    backgroundColor: '#fff2a8',
  })

  customizer.editText(textId)
}

传入 backgroundColor: null 可以恢复透明文字背景。消费页面必须在使用或恢复自定义字体前完成字体加载

对象与图层 API

方法用途
getObjects()按从后到前的顺序返回对象
getSelectedObjectIds()返回当前选区中的稳定 ID
selectObject(id)选中一个可见对象
selectObjects(ids)多选可见对象;传入空数组会清除选区
clearSelection()清除当前选区且不修改设计内容
removeObject(id)根据 ID 删除一个对象
moveObject(id, index)将对象移动到从 0 开始的图层索引
renameObject(id, name)修改面向业务的图层名称
setObjectVisibility(id, visible)控制对象是否参与渲染
setObjectLocked(id, locked)允许或禁止画布变换
updateObjectTransform(id, options)修改对象中心位置、缩放、旋转和翻转
updateText(id, options)按稳定 ID 修改文字内容和排版样式
editText(id)让未锁定文字进入画布内编辑状态
deleteSelected()删除当前对象或选区

这些方法是自定义图层面板应使用的边界

状态与几何信息

自定义界面应读取状态快照,不要查询 Workbench DOM 或底层渲染库对象

ts
const state = customizer.getState()
const product = customizer.getProduct()
const canvas = customizer.getCanvasSize()
const printableBounds = customizer.getPrintableBounds()

customizer.selectObjects(state.objects.slice(-2).map(({ id }) => id))
const firstObject = state.objects[0]
if (firstObject) {
  customizer.updateObjectTransform(firstObject.id, {
    x: printableBounds.left + printableBounds.width / 2,
    y: printableBounds.top + printableBounds.height / 2,
    rotation: 12,
  })
}

所有坐标均使用配置的逻辑画布,不使用 CSS 显示尺寸。返回的状态、商品、几何信息、对象和视角值都是互不影响的快照

保存和恢复设计

ts
const design = customizer.saveDesign()
localStorage.setItem('product-design', JSON.stringify(design))

const saved = localStorage.getItem('product-design')
if (saved) {
  await customizer.loadDesign(JSON.parse(saved))
}

当前 Schema 版本为 1

Design JSON 包含逻辑画布尺寸、可编辑对象和丰富的文字排版属性,不包含商品模型、目标 Mesh 和商品基础纹理

Schema version 1 中的排版字段保持可选。缺少这些字段的旧文档仍会使用原有的粗体、居中文字默认值加载

加载过程具有事务性,无效文档或图片加载失败不会替换当前设计

恢复设计时,远程图片地址必须继续可用并允许跨域访问

历史记录

ts
if (customizer.canUndo()) {
  await customizer.undo()
}

if (customizer.canRedo()) {
  await customizer.redo()
}

customizer.clearHistory()

订阅历史状态可以更新自己的操作控件

ts
const stopHistory = customizer.on(
  'historychange',
  ({ canUndo, canRedo }) => {
    undoButton.disabled = !canUndo
    redoButton.disabled = !canRedo
  },
)

事件

事件载荷用途
ready商品模型和纹理完成加载
change设计内容或图层状态发生变化
selectionchange选中对象 ID 发生变化
historychange撤销或重做可用状态发生变化
viewchange三维相机位置或观察目标点发生变化
status加载或运行状态信息发生变化
error模型、纹理或图片处理失败

每次调用 on() 都会返回取消订阅函数

ts
const stopErrors = customizer.on('error', ({ error }) => {
  console.error(error)
})

stopErrors()
stopHistory()

商品和输出控制

ts
await customizer.loadProduct({
  modelUrl: 'https://cdn.example.com/another-product.glb',
  surfaceMesh: 'PrintArea',
})

const pngUrl = customizer.getTextureDataUrl()
const pngBlob = await customizer.getTextureBlob()
customizer.exportTexture('product-design.png')

const view = customizer.getViewState()
customizer.setViewState(view)
customizer.resetView()

商品切换成功后会保留当前设计对象,并以当前设计重新开始历史记录

Data URL、Blob 和下载的 PNG 都使用逻辑画布分辨率,并排除选择控件和 UV 辅助图形。远程图片必须允许跨域访问,否则浏览器 Canvas 安全规则会拒绝输出

API 边界

应用服务和组件应使用导出的 ProductCustomizerApi 类型。具体的 ProductCustomizer 类仍然可用,但自定义 UI 代码只应依赖本文记录的接口和事件

核心层有意不公开 Fabric.js Canvas 对象、Three.js 场景和 Workbench DOM,使自定义控件与样式不受内部依赖升级影响

清理资源

ts
customizer.destroy()

实例不再使用时调用一次 destroy(),释放事件监听器、Fabric 状态、观察器和 WebGL 资源