jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Three.js from Zero to Senior · Part 10 — Capstone: Production & Deployment

Tie it all together into a shippable mini-project: loading screens, ResizeObserver responsiveness, a render-loop architecture that scales, framework integration, debug tooling, and a real deployment checklist — with a full capstone demo.

5 MIN READ Updated JUL 12, 2026

Bạn đã dựng các mảnh — scene, vật liệu, đèn, model, animation, hậu kỳ, hiệu năng, tương tác. Việc cuối của một senior là lắp ráp: biến các mảnh đó thành thứ nạp mượt, resize đúng, sống sót trong framework, và ship được.

Scene tổng kết này dùng mọi thứ trong series cùng lúc — màn hình nạp, mặt trời rực bloom, vành đai thiên thạch instanced, hành tinh click-để-chọn, resize responsive, và HUD debug trực tiếp. Click một hành tinh; bật HUD debug để xem renderer.info.

Mở demo toàn màn hình

Kiến trúc render-loop có thể mở rộng

Mẫu đồ chơi — một hàm animate() khổng lồ — sụp khi vượt vài trăm dòng. Một app Three.js dễ bảo trì tách mối quan tâm thành một bộ object nhỏ:

class World {
  constructor(canvas) {
    this.canvas = canvas;
    this.scene = new THREE.Scene();
    this.camera = createCamera();
    this.renderer = createRenderer(canvas);
    this.composer = createComposer(this.renderer, this.scene, this.camera);
    this.timer = new THREE.Timer();
    this.timer.connect(document);
    this.updatables = [];          // anything with a .tick(dt) method
    this.#bindResize();
  }
  add(obj) { this.scene.add(obj); if (obj.tick) this.updatables.push(obj); }
  start() { this.renderer.setAnimationLoop((t) => this.#frame(t)); }
  stop()  { this.renderer.setAnimationLoop(null); }   // ← crucial for SPAs
  #frame(timestamp) {
    this.timer.update(timestamp);
    const dt = this.timer.getDelta();
    for (const o of this.updatables) o.tick(dt);
    this.composer.render();
  }
}

Hai chi tiết senior ẩn ở đây:

  • renderer.setAnimationLoop() thay cho requestAnimationFrame trần để tương thích tốt với renderer và WebXR. Nó không phải lifecycle policy: app vẫn cần chủ động suspend/resume theo visibility, viewport và route.
  • Một danh sách updatables để mỗi object tự sở hữu logic mỗi frame qua tick(dt) — không có hàm trung tâm biết hết mọi thứ.

Màn hình nạp — quản lý sự chờ, đừng giấu nó

Asset thật mất thời gian, và một canvas đen đứng hình bị hiểu là “hỏng.” Three.js cho bạn một LoadingManager toàn cục bắn callback tiến trình và hoàn tất xuyên mọi loader:

const manager = new THREE.LoadingManager();
manager.onProgress = (url, loaded, total) => setBar(loaded / total);
manager.onLoad = () => hideLoadingScreen();   // all assets ready → reveal

const gltfLoader = new GLTFLoader(manager);    // pass the manager to each loader
const texLoader  = new THREE.TextureLoader(manager);

Nối mọi loader vào một manager, hiện trạng thái từ onProgress, và hé lộ scene khi onLoad. Tỉ lệ loaded / totalsố item, không phải số byte; muốn byte progress cần đi qua fetch/response stream hoặc manifest có kích thước. Demo tổng kết chỉ mô phỏng trạng thái chờ, không được dùng để khẳng định pipeline asset thật đã được đo.

Responsive — ResizeObserver, không phải window ‘resize’

Sự kiện resize của window bỏ lỡ các trường hợp phổ biến: canvas trong panel flex/grid, sidebar thu lại, iframe nhúng. Hãy quan sát chính canvas:

const ro = new ResizeObserver(() => {
  const { clientWidth: w, clientHeight: h } = canvas;
  if (!w || !h) return;                       // skip while hidden (0×0)
  renderer.setSize(w, h, false);              // false = don't touch CSS size
  composer.setSize(w, h);                     // keep the composer in sync
  camera.aspect = w / h; camera.updateProjectionMatrix();
});
ro.observe(canvas);

Cờ false trên setSize quan trọng khi canvas được CSS định kích thước — bạn muốn WebGL khớp với phần tử, không ghi đè style của nó.

Tích hợp vào framework

Three.js là mệnh lệnh; React/Vue/Svelte là khai báo. Quy tắc cầu nối đơn giản: tạo World một lần, không bao giờ trong render, và luôn dọn dẹp khi unmount.

function Scene() {
  const ref = useRef(null);
  useEffect(() => {
    const world = new World(ref.current);
    world.start();
    return () => { world.stop(); world.dispose(); }; // stop loop + free GPU memory
  }, []);                                            // empty deps → run once
  return <canvas ref={ref} />;
}

Quên dọn dẹp thì SPA rò rỉ cả một WebGL context (và VRAM của nó) mỗi lần điều hướng. Nếu bạn viết nhiều React, react-three-fiber làm cầu nối này cho bạn — nhưng biết nó tự động hoá cái gì chính là thứ phân biệt senior với làm theo kiểu mê tín.

Công cụ debug

Không thể tinh chỉnh thứ bạn không thấy. Senior nối những thứ này từ sớm:

  • renderer.info trên màn hình — draw call, tam giác, geometry, texture.
  • Một panel GUI (lil-gui) gắn với giá trị trực tiếp để tinh chỉnh không cần sửa code.
  • Helper khi phát triển — gỡ khi production.
  • window.scene = scene trong dev để chọc graph từ console.

Checklist triển khai

Trước khi ship một scene Three.js:

  • Nén asset — Draco cho mesh, KTX2/Basis cho texture, .glb hơn .gltf.
  • Giới hạn pixel ratio ở 2 và test trên điện thoại tầm trung thật.
  • Tôn trọng prefers-reduced-motion — chậm hoặc dừng animation nhàn rỗi.
  • Xử lý WebGL lỗi — kiểm tra và hiện ảnh dự phòng, đừng để canvas trắng.
  • Lazy-load bundle 3D nặng để không chặn lần vẽ đầu.
  • Dispose khi unmount và xác nhận renderer.info.memory phẳng qua các lần điều hướng.
  • Tone mapping + quản lý màu bật, để production trông như dev.

Lời cảnh báo của sư phụ

  • Scene trắng sau khi đổi route? Bạn tạo lại World trong render, hoặc chưa dispose cái cũ.
  • Canvas không resize trong panel? Dùng ResizeObserver trên canvas.
  • Animation chạy hoài ngoài viewport? Dùng visibilitychange/IntersectionObserver để stop()start() tường minh; setAnimationLoop không thay policy đó.
  • Tốt trên desktop, chảy trên mobile? Pixel ratio và asset chưa nén.

Checkpoint — từ feature sang production engineering

Bạn đã đi từ canvas trắng tới một scene tương tác có loading state, resize, lifecycle và debug HUD. Đây là checkpoint, chưa phải đích: các phần sau đi vào shader, physics, asset pipeline, animation nhân vật, toán không gian, CPU/GPU profiling, ownership, testing và WebGPU/TSL.

Từ đây tiêu chuẩn đổi: không chỉ làm cho scene chạy, mà phải chứng minh nó giữ budget, recover được và có contract để cả team cùng phát triển.