大家好, 我是徐小夕. 

上周和大家分享了多维表格 flowmix/mute 相关的内容,也上线了第一个版本供大家参考体验:

图片

体验地址:http://mute.turntip.cn

相关文章如下:

一款轻量且功能强大的可视化多维表格Mute, 正式上线!

花了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采用了组件化、模块化的架构设计,主要分为以下几个部分:

架构图

数据流

  1. 状态管理

    使用Zustand管理应用状态,包括场景元素、选中元素等

  2. 用户交互

    用户通过界面进行交互,如拖拽组件、选择元素、调整属性等

  3. 状态更新

    交互触发状态更新,通过Zustand的actions修改状态

  4. UI渲染

    状态变化触发UI重新渲染,包括3D场景和编辑界面

  5. 历史记录

    状态变化被记录到历史栈中,支持撤销/重做操作

核心功能实现

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 &lt; 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视界 公众号获取最新更新的信息.

往期更新:

Flowmix/Docx 多模态文档编辑器: 让文档不止于文档

MindLink,一款功能强大的AI文档编辑器

多模态文档+思维导图:引领内容创作新潮流

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐