體驗地址:http://mute.turntip.cn
整個搭建平臺核心模塊包含如下幾個部分:
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設置
<Canvascamera={{ position: viewMode === "3D" ? [5, 5, 5] : [0, 5, 0], fov: 50 }}shadowsclassName="w-full h-full"
><ambientLight intensity={0.5} /><directionalLight position={[10, 10, 10]} intensity={1} castShadow />{/* 網格 */}<Gridargs={[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) => (<SceneObjectkey={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提供了浮動工具欄,允許用戶對選中的元素進行編輯操作,如移動、旋轉、復制和刪除等。
// 浮動工具欄定位
<divref={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",}}
>{/* 工具按鈕 */}<buttonclassName={`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
flowmix/docx多模態文檔引擎,目前也在持續更新中,歡迎體驗參考:https://flowmix.turntip.cn
多維表格 flowmix/mute
體驗地址:http://mute.turntip.cn