如何实现Three.js模型上Fabric.js对象的拖拽与缩放功能
实现方案
你已经完成了3D射线检测点选Fabric对象的核心逻辑,拖拽和缩放可以直接基于现有逻辑扩展,以下是可直接落地的实现方案:
1. 拖拽功能实现
核心思路是复用已有的射线检测能力,在拖拽过程中实时同步3D视图点击坐标到Fabric画布坐标:
- 首先新增3个状态变量,用于标记拖拽状态、当前选中对象、拖拽偏移量
- 选中对象时记录点击坐标和对象左上角的偏移量,避免拖拽时对象出现跳变
- 监听鼠标移动事件,拖拽状态下实时获取当前点击位置的UV坐标,转换为画布坐标后更新选中对象的位置,同时同步刷新3D材质纹理
参考代码如下:
// 新增状态变量 let isDragging = false; let selectedFabricObj = null; let dragOffset = { x: 0, y: 0 }; // 修改你原有选中逻辑部分,补充偏移量记录 if (selected != null) { selectedFabricObj = selected; // 计算点击位置和对象左上角的偏移 const cp = { x: uv.x * 512, y: uv.y * 512 }; dragOffset.x = cp.x - selected.left; dragOffset.y = cp.y - selected.top; controls.enabled = false; canvas.setActiveObject(selected); canvas.renderAll(); } else { selectedFabricObj = null; canvas.discardActiveObject().renderAll(); controls.enabled = true; } // 新增鼠标事件监听 raycastContainer.addEventListener('mousedown', () => { if (selectedFabricObj) isDragging = true; }); raycastContainer.addEventListener('mousemove', (evt) => { if (!isDragging || !selectedFabricObj) return; evt.preventDefault(); // 复用原有射线检测逻辑获取当前UV const array = getMousePosition(raycastContainer, evt.clientX, evt.clientY); onClickPosition.fromArray(array); const intersects = getIntersects(onClickPosition, object.children); if (intersects.length > 0 && intersects[0].uv) { const uv = intersects[0].uv; intersects[0].object.material.map.transformUv(uv); // 转换UV为画布坐标,减去偏移量得到对象新位置 const currentX = uv.x * 512 - dragOffset.x; const currentY = uv.y * 512 - dragOffset.y; // 更新Fabric对象位置 selectedFabricObj.set({ left: currentX, top: currentY }); canvas.renderAll(); // 同步刷新3D纹理 intersects[0].object.material.map.needsUpdate = true; } }); raycastContainer.addEventListener('mouseup', () => { isDragging = false; });
2. 缩放功能实现
推荐直接复用Fabric.js原生的控制控件,开发成本最低:
- 初始化Fabric画布时开启选择控件和控件悬浮层级
- 监听对象缩放事件,每次缩放后同步更新3D材质即可
参考代码如下:
// 初始化画布时开启控件配置 const canvas = new fabric.Canvas('你的画布ID', { selection: true, controlsAboveOverlay: true, // 保留你原有的其他配置 }); // 监听对象缩放事件,同步更新3D纹理 canvas.on('object:scaling', (e) => { const activeObj = e.target; if (activeObj) { object.children.forEach(mesh => { if (mesh.material.map) mesh.material.map.needsUpdate = true; }); } });
注意事项
- 拖拽过程中要保持3D轨道控制器处于禁用状态,避免和拖拽操作冲突
- 如果出现Y轴拖拽方向相反的问题,是因为Three.js UV默认Y轴从下往上,Fabric画布Y轴从上往下,把坐标转换时的
uv.y替换为1 - uv.y即可 - 可补充边界判断逻辑,避免对象被拖拽出画布范围,比如限制
left >= 0、top >=0、left + obj.width <= 512、top + obj.height <=512
内容的提问来源于stack exchange,提问作者naoval luthfi
相关产品推荐
相关产品推荐

