Building a Next-Generation 3D Web Viewer with Three.js: Lightweight, Modular, and Fully Interactive
Today, web-based 3D applications are no longer limited to static visualizations. A real 3D experience demands dynamic interaction, professional-level architecture, and lightweight performance. In this article, I explain how I built a high-quality 3D Viewer by using only Three.js — without relying on heavy external libraries like React-Three-Fiber — integrating essential features like model loading (Loader), object selection (Raycaster), a window management system, and a layered scene manager.
The Story: Why I Built This Infrastructure
I was tasked with creating a web application where users could select cities, draw on top of them, and manage these drawings through multiple layers. The goal was to allow users to build detailed 3D city-based models, add and remove objects, and fully control their scenes.
Existing solutions were either too heavy or lacked the necessary customization capabilities. Thus, I decided to build my own infrastructure:
-
Lightweight and independent,
-
Easily extendable for future needs,
-
Offering maximum real-time interaction,
-
Enabling city and layer-based scene control,
-
Providing complete ownership and customization flexibility.
To achieve this, I meticulously designed a system based on Loader, Raycaster, Window Manager, Layer Manager, and a centralized Viewer Store.
Why I Chose Zustand
I opted for Zustand for global state management because it offers a minimal and modular approach compared to heavier alternatives like Redux. With Zustand, I achieved:
-
Clear and readable code structures,
-
Minimal external dependencies,
-
High-performance state sharing for critical objects like scene, camera, and renderer,
-
A scalable store design split into manageable pieces.
This helped me maintain a clean, maintainable, and high-performing application even as the project grew in complexity.
Targeted Architecture
My goal was to establish an infrastructure with:
-
Modular and readable code organization
-
Visual feedback during loading (progress tracking)
-
Real-time object interaction (hover, click, etc.)
-
Dynamic UI control through a Window Manager
-
Visibility management through a Layer Manager
-
Centralized global state management with Viewer Store
-
Minimal dependencies and maximum control
Development: Code Principles, Structure, and Quality
Key coding principles I applied in this project include:
-
Single Responsibility Principle: Each module handles its own task only.
-
Asynchronous Management: Users receive real-time loading feedback and proper error handling.
-
Real-Time Interaction: Raycaster dynamically detects objects under the mouse on every frame.
-
Window Manager: Each window (e.g., Layer Manager, Debug Panel) can register and be opened/closed dynamically.
Example Window Manager snippet:
class WindowManager {
static windows = [];
static openWindow(window) {
if (!this.windows.includes(window)) {
this.windows.push(window);
}
}
static closeWindow(window) {
this.windows = this.windows.filter(w => w !== window);
}
}
-
Layer Manager: Objects are assigned to layers, and users can toggle their visibility through the UI.
Example Layer Manager snippet:
function toggleLayerVisibility(layerNumber, scene) {
scene.children.forEach(object => {
if (object.layers.test(new THREE.Layers().set(layerNumber))) {
object.visible = !object.visible;
}
});
}
-
Viewer Store: Global objects like scene, camera, and renderer are centrally managed using Zustand.
Example Viewer Store snippet:
import create from 'zustand';
export const useViewerStore = create((set) => ({
scene: null,
camera: null,
renderer: null,
setScene: (scene) => set({ scene }),
setCamera: (camera) => set({ camera }),
setRenderer: (renderer) => set({ renderer }),
}));
-
Performance-First Rendering: Updates occur only when absolutely necessary.
-
Memory Management: Unused objects are properly disposed to prevent memory leaks.
Conclusion
This Canvas + Three.js architecture offers a rock-solid foundation for anyone aiming to build a modern 3D web viewer. By keeping the code clean and modular while integrating critical systems like Loader, Raycaster, Window Manager, Layer Manager, and Zustand-based Viewer Store, I enabled future-proof scalability and professional-grade interaction.
In the next stages, potential improvements could include:
-
Zooming the camera to an object upon click,
-
Enhancing loading animations,
-
Introducing multi-layer and multi-scene support,
-
Adding window minimization and restoration features,
-
Saving and loading scene settings through the Viewer Store.
Related Articles
Same CategoryComments (0)
Newsletter
Stay updated! Get all the latest and greatest posts delivered straight to your inbox