写了一款3D可视化编辑器模版,开源!
今年我打算花半年时间,打造一个可视化解决方案的技术专栏,我会分享关于可视化相关的各种解决方案以及技术架构设计,帮我大家快速提升技术架构能力,并能更深入的理解低代码,可视化的技术实现。比较感兴趣,想学习一下设计思路,所以我就写了一个基础的,并且流程完整的3D可视化搭建项目,大家可以基于我设计的这套框架,来二次扩展自己的3D可视化搭建平台。i3D Editor提供了浮动工具栏,允许用户对选中的元素进行
大家好, 我是徐小夕.
上周和大家分享了多维表格 flowmix/mute 相关的内容,也上线了第一个版本供大家参考体验:

体验地址:http://mute.turntip.cn
相关文章如下:
花了2个月时间研究了市面上的4款开源表格组件,崩溃了,决定自己写一款
最近抽空又写了一个3D可视化编辑器的模版 i3D-Editor,我们可以使用它轻松构建3D可视化编辑器,目前已经在github上开源,文末会附带github地址。

接下来就和大家分享一下这款开源3D可视化编辑器模版以及核心功能实现。
demo演示

之所以写这个项目,是因为之前有小伙伴对我的专栏《架构师精选》中分享的3D可视化项目比较感兴趣,想学习一下设计思路,所以我就写了一个基础的,并且流程完整的3D可视化搭建项目,大家可以基于我设计的这套框架,来二次扩展自己的3D可视化搭建平台。
整个搭建平台核心模块包含如下几个部分:
-
3D场景渲染
-
组件拖拽系统
-
元素编辑功能
-
状态管理
-
历史记录与撤销/重做
技术栈
前端框架与库
- React 18
用于构建用户界面的JavaScript库
- Next.js 14
React框架,提供服务端渲染、路由等功能
- TypeScript
静态类型检查的JavaScript超集
- Three.js
3D图形库,用于在浏览器中渲染3D场景
- React Three Fiber
Three.js的React渲染器
- React Three Drei
Three.js的React组件集合
- Tailwind CSS
实用优先的CSS框架
- Lucide React
现代图标库
状态管理与工具
- Zustand
轻量级状态管理库
- UUID
用于生成唯一标识符
- HTML5 Drag and Drop API
原生拖放功能实现
技术架构

i3D Editor采用了组件化、模块化的架构设计,主要分为以下几个部分:
架构图

数据流
- 状态管理
使用Zustand管理应用状态,包括场景元素、选中元素等
- 用户交互
用户通过界面进行交互,如拖拽组件、选择元素、调整属性等
- 状态更新
交互触发状态更新,通过Zustand的actions修改状态
- UI渲染
状态变化触发UI重新渲染,包括3D场景和编辑界面
- 历史记录
状态变化被记录到历史栈中,支持撤销/重做操作
核心功能实现
3D场景渲染
i3D Editor使用React Three Fiber和React Three Drei来简化Three.js的使用,实现3D场景的渲染。
// Canvas设置<Canvas camera={{ position: viewMode === "3D" ? [5, 5, 5] : [0, 5, 0], fov: 50 }} shadows className="w-full h-full"> <ambientLight intensity={0.5} /> <directionalLight position={[10, 10, 10]} intensity={1} castShadow />
{/* 网格 */} <Grid args={[100, 100]} cellSize={1} cellThickness={0.5} cellColor="#a0a0ff" sectionSize={5} sectionThickness={1} sectionColor="#2080ff" fadeDistance={50} fadeStrength={1.5} followCamera={false} infiniteGrid />
{/* 场景对象 */} {elements.map((element) => ( <SceneObject key={element.id} element={element} isSelected={selectedElement?.id === element.id} onClick={() => setSelectedElement(element)} viewMode={viewMode} activeMode={activeMode} /> ))}
{/* 控制器 */} <OrbitControls makeDefault enabled={!selectedElement || activeMode !== "select"} />
<Environment preset="studio" /></Canvas>
组件拖拽系统
i3D Editor实现了一个基于HTML5 Drag and Drop API的拖拽系统,允许用户从组件库拖拽元素到3D场景中。
// 拖拽开始const handleDragStart = (event, elementType) => { event.dataTransfer.setData("application/element-type", elementType); event.dataTransfer.effectAllowed = "copy";
// 通知父组件拖拽开始 onStartDrag(elementType);};// 拖拽结束const handleDrop = (event) => { event.preventDefault();
if (!isDragging || !draggedElementType) return;
// 获取Canvas容器的位置和尺寸 const rect = canvasContainerRef.current.getBoundingClientRect();
// 计算鼠标在Canvas中的相对位置 const x = ((event.clientX - rect.left) / rect.width) * 2 - 1; const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
// 创建射线并计算与平面的交点 const raycaster = new THREE.Raycaster(); raycaster.setFromCamera( new THREE.Vector2(x, y), new THREE.PerspectiveCamera(50, rect.width / rect.height, 0.1, 1000) );
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0); const target = new THREE.Vector3(); raycaster.ray.intersectPlane(plane, target);
// 添加元素到场景 addElement({ type: draggedElementType, position: { x: target.x, y: 0, z: target.z }, rotation: { x: 0, y: 0, z: 0 }, scale: 1, material: "standard", color: getDefaultColor(draggedElementType), name: getElementName(draggedElementType), displayTip: false, });
// 重置拖拽状态 setIsDragging(false); setDraggedElementType(null);};
元素编辑功能
i3D Editor提供了浮动工具栏,允许用户对选中的元素进行编辑操作,如移动、旋转、复制和删除等。
// 浮动工具栏定位<div ref={toolbarRef} className={`absolute bg-white rounded-lg shadow-lg z-20 flex ${isHorizontalLayout ? "flex-row" : "flex-col"} items-center p-2`} style={{ left: `${toolbarPosition.x}px`, top: `${toolbarPosition.y}px`, transform: "translate(-50%, -120%)", transition: "left 0.2s ease, top 0.2s ease", }}> {/* 工具按钮 */} <button className={`p-1.5 rounded-full hover:bg-blue-100 ${activeMode === "select" ? "text-blue-500" : ""}`} onClick={() => handleToolClick("select")} title="选择工具" > <MousePointer className="h-4 w-4" /> </button>
{/* 更多工具按钮... */}</div>
状态管理实现
i3D Editor使用Zustand进行状态管理,包括场景元素、选中元素等。
// 元素存储export const useElementStore = create<ElementStore>((set) => ({ elements: [], selectedElement: null,
addElement: (element) => set((state) => ({ elements: [...state.elements, { ...element, id: uuidv4() }], })),
updateElement: (id, updates) => set((state) => ({ elements: state.elements.map((element) => element.id === id ? { ...element, ...updates } : element ), selectedElement: state.selectedElement?.id === id ? { ...state.selectedElement, ...updates } : state.selectedElement, })),
removeElement: (id) => set((state) => ({ elements: state.elements.filter((element) => element.id !== id), selectedElement: state.selectedElement?.id === id ? null : state.selectedElement, })),
setSelectedElement: (element) => set({ selectedElement: element }),
// 更多actions...}));
历史记录与撤销/重做

i3D Editor实现了历史记录功能,支持撤销和重做操作。
// 历史记录状态const [history, setHistory] = useState<HistoryState[]>([]);const [historyIndex, setHistoryIndex] = useState(-1);const [isUndoRedo, setIsUndoRedo] = useState(false);// 监听元素变化,更新历史记录useEffect(() => { if (isUndoRedo) { setIsUndoRedo(false); return; } if (historyIndex >= 0) { const currentState: HistoryState = { elements: JSON.parse(JSON.stringify(elements)), selectedElementId: selectedElement?.id || null, }; // 检查是否与当前历史记录状态相同 const lastState = history[historyIndex]; const isEqual = JSON.stringify(lastState.elements) === JSON.stringify(currentState.elements) && lastState.selectedElementId === currentState.selectedElementId; if (!isEqual) { // 如果在历史记录中间进行了操作,则删除后面的历史记录 const newHistory = history.slice(0, historyIndex + 1); setHistory([...newHistory, currentState]); setHistoryIndex(historyIndex + 1); } }}, [elements, selectedElement, history, historyIndex]);// 撤销操作const handleUndo = () => { if (historyIndex > 0) { setIsUndoRedo(true); const prevState = history[historyIndex - 1]; // 应用历史状态 loadElements(prevState.elements); // 恢复选中状态 if (prevState.selectedElementId) { const selectedElement = prevState.elements.find( (el) => el.id === prevState.selectedElementId ); if (selectedElement) { setSelectedElement(selectedElement); } } else { setSelectedElement(null); } setHistoryIndex(historyIndex - 1); }};// 重做操作const handleRedo = () => { if (historyIndex < history.length - 1) { setIsUndoRedo(true); const nextState = history[historyIndex + 1]; // 应用历史状态 loadElements(nextState.elements); // 恢复选中状态 if (nextState.selectedElementId) { const selectedElement = nextState.elements.find( (el) => el.id === nextState.selectedElementId ); if (selectedElement) { setSelectedElement(selectedElement); } } else { setSelectedElement(null); } setHistoryIndex(historyIndex + 1); }};
上面就是核心功能实现,当然项目还有很多需要优化,这里只是给大家提供一个方案思路参考,如果感兴趣可以在github上下载代码学习。
github地址:https://github.com/MrXujiang/3D-Editor
关于我的付费专栏
今年我打算花半年时间,打造一个可视化解决方案的技术专栏,我会分享关于可视化相关的各种解决方案以及技术架构设计,帮我大家快速提升技术架构能力,并能更深入的理解低代码,可视化的技术实现。
同时在这个专栏中,我会全面复盘之前在中大厂的职场技术复盘,以及最新的AI技术实践和职场进化方法论,感兴趣的可以加入我们一起成长学习:

同时为了方便小伙伴更好的学习交流,我建立了一个可视化+AI技术交流群,大家感兴趣也可以加入学习交流:

最后
我们最近研发的 flowmix/docx多模态文档引擎,目前也在持续更新中,欢迎体验参考:

https://flowmix.turntip.cn
每个月我们都会根据用户的需求和规划的迭代计划持续迭代, 大家可以关注flowmix视界 公众号获取最新更新的信息.
往期更新:
更多推荐



所有评论(0)