🍔 fabric如何实现辅助选区捏

功能概述

基本流程:以不同的颜色分区作为依据,解析图片数据,帮助辅助选区

最终效果:

1.1 核心功能

  • 图像追踪:将位图选区图片转换为 SVG 矢量路径

  • 路径解析:按颜色分组解析 SVG 路径数据

  • 交互选择:支持鼠标点击、悬停等交互操作

1.2 技术栈

  • Fabric.js:Canvas 图形库,用于路径渲染和交互

  • imageTracer.js:图像追踪库,用于位图转矢量


核心实现流程

主函数:test()

test() 函数是选区识别与交互功能的核心入口,完整流程如下:

初始化画布状态

ini 复制代码
selectionLoading.value = true // 显示加载提示
fabricCanvas.skipTargetFind = false // 启用对象检测
fabricCanvas.isDrawingMode = false // 禁用绘制模式
fabricCanvas.renderAll() // 重新渲染

关键点

  • skipTargetFind: false 允许鼠标事件被路径对象捕获
  • isDrawingMode: false 确保不会触发自由绘制

加载选区图片

此处需要一张以不同颜色区分选区的图片,后续将基于这些颜色进行区域识别:

ini 复制代码
const img = new Image()
img.crossOrigin = 'anonymous' // 跨域支持
img.src = props.selectionUrl

await new Promise((resolve) => {
  img.onload = resolve
})

提取选区图片像素数据

ini 复制代码
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = img.width
canvas.height = img.height
ctx.drawImage(img, 0, 0)

创建临时 Canvas 获取图片的像素数据

将图片canvas的数据导入进 imageTracer;将其转化为svg,

arduino 复制代码
const svgString = imageTracer.imageDataToSVG(ctx?.getImageData(0, 0, canvas.width, canvas.height), {
  ltres: 1, // 直线阈值
  qtres: 1, // 二次曲线阈值
  pathomit: 8, // 路径简化阈值
})

参数说明

  • ltres:控制直线检测的精度,值越小越精确
  • qtres:控制曲线检测的精度
  • pathomit:路径简化参数,值越大简化程度越高

SVG 格式的字符串,包含多个 <path> 元素。这些path元素记录了我们需要的区域信息,比方说在这张图的那张位置,边界是如何巴拉巴拉。

<path> 元素提供了精确的区域信息,将其导入fabric的path中,利用fabric的交互监听,可以帮助进行处理

解析 SVG 并按颜色分组

typescript 复制代码
const parser = new DOMParser()
const svgDoc = parser.parseFromString(svgString, 'image/svg+xml')
const pathGroupByColor = new Map<string, string>()

svgDoc.querySelectorAll('path').forEach((path) => {
  const color = path.getAttribute('fill') || '#000000'
  // 过滤黑色背景
  if (color === 'rgb(0,0,0)' || color === '#000000') {
    return
  }
  const pathData = path.getAttribute('d') || ''
  const oriVal = pathGroupByColor.get(color) || ''
  pathGroupByColor.set(color, oriVal + ' ' + pathData)
})

处理逻辑

  1. 使用 DOMParser 解析 SVG 字符串为 DOM 对象
  2. 遍历所有 <path> 元素
  3. fill 颜色属性分组,相同颜色的路径数据合并
  4. 过滤掉黑色背景(#000000rgb(0,0,0)

为什么按颜色分组

  • 图像追踪可能产生多个不同颜色的路径区域
  • 相同颜色的路径通常属于同一个选区区域
  • 合并后可以减少路径对象数量,提高性能

创建可交互的 Fabric Path 对象

php 复制代码
pathGroupByColor.forEach((pathData) => {
 const fabricPath = new fabric.Path(pathData, {
  fill: 'transparent',
  opacity: 0.01,
  stroke: 'transparent',
  strokeWidth: 1,
    perPixelTargetFind: true, // 像素级精确检测
  })

  // 绑定交互事件...
  selectionPaths.value.push(fabricPath)
})

关键配置

  • perPixelTargetFind: true:启用像素级命中检测,提高点击精度

必须使用opacity: 0.01,因为此处是使用perPixelTargetFind: true 进行像素检测,如果说完全透明+无背景,fabric无法监测这个path的交互事件

绑定鼠标交互事件

dart 复制代码
const initPtah = {
  fill: 'white',
  opacity: 0.01,
  stroke: 'transparent',
  strokeWidth: 1,
};
const selectedPath = {
  fill: pencilBrushColor.value,
  globalCompositeOperation: 'xor',
  stroke: pencilBrushColor.value,
  opacity: 1,
};
// 点击
fabricPath.on('mousedown', (e: any) => {
  const opacity = fabricPath.get('opacity')
  const pathOption = opacity === 1 ? initPtah : selectedPath

  fabricPath.set({
    ...pathOption,
  })
  fabricCanvas.renderAll()
})

// hover:
fabricPath.on('mouseover', (e: any) => {
  const opacity = fabricPath.get('opacity')
  if (opacity === 1) return // 已选中的不处理

  fabricPath.set({
    ...selectedPath,
    opacity: 0.75, // 半透明高亮
  })
  fabricCanvas.renderAll()
})

// leave
fabricPath.on('mouseout', (e: any) => {
  const opacity = fabricPath.get('opacity')
  if (opacity === 1) return // 已选中的保持选中状态

  fabricPath.set({
    ...initPtah, // 恢复初始透明状态
  })
  fabricCanvas.renderAll()
})

组合路径并适配画布

arduino 复制代码
const scaleX = canvasWidth.value / img.width!
const scaleY = canvasHeight.value / img.height!
const scale = Math.min(scaleX, scaleY) // 保持宽高比
const group = new fabric.Group(selectionPaths.value, {
  evented: true, // 启用事件
  hasControls: false, // 隐藏控制点
  subTargetCheck: true, // 子对象检测
})

group.set({
  scaleX: scale,
  scaleY: scale,
  left: (canvasWidth.value - img.width! * scale) / 2, // 水平居中
  top: (canvasHeight.value - img.height! * scale) / 2, // 垂直居中
  lockMovementX: true, // 锁定水平移动
  lockMovementY: true, // 锁定垂直移动
})

缩放计算

  • 计算水平和垂直方向的缩放比例
  • 取较小值确保图片完整显示且保持宽高比

Group 配置

  • evented: true:允许接收鼠标事件
  • subTargetCheck: true:检测子对象(路径),确保点击路径时能触发事件
  • hasControls: false:隐藏变换控制点,防止用户误操作
  • lockMovementX/Y: true:锁定位置,防止拖动
相关推荐
我的写法有点潮4 小时前
JS中对象是怎么运算的呢
前端·javascript·面试
悠哉摸鱼大王4 小时前
NV12 转 RGB 完整指南
前端·javascript
一壶纱4 小时前
UniApp + Pinia 数据持久化
前端·数据库·uni-app
双向334 小时前
【RAG+LLM实战指南】如何用检索增强生成破解AI幻觉难题?
前端
海云前端14 小时前
前端人必懂的浏览器指纹:不止是技术,更是求职加分项
前端
青莲8434 小时前
Java内存模型(JMM)与JVM内存区域完整详解
android·前端·面试
parade岁月4 小时前
把 Git 提交变成“可执行规范”:Commit 规范体系与 Husky/Commitlint/Commitizen/Lint-staged 全链路介绍
前端·代码规范
青莲8434 小时前
Java内存回收机制(GC)完整详解
java·前端·面试
pas1364 小时前
29-mini-vue element搭建更新
前端·javascript·vue.js
IT=>小脑虎4 小时前
2026版 React 零基础小白进阶知识点【衔接基础·企业级实战】
前端·react.js·前端框架