← Tất cả bài viết
React Three Fiber Cho Người Mới Bắt Đầu
Hướng dẫn React Three Fiber cho người mới — dựng Canvas, tạo mesh, animate bằng useFrame, thêm ánh sáng, OrbitControls và load model 3D, kèm các lỗi thường gặp.

Nếu bạn viết được React, bạn viết được 3D. Đó chính là lời hứa của React Three Fiber — một
renderer React cho three.js, biến đống boilerplate WebGL rườm rà thành các component khai báo
(declarative) mà bạn đã quen tư duy. Bài React Three Fiber cho người mới này sẽ dẫn bạn từ một
<Canvas> trống đến một scene có ánh sáng, xoay được bằng chuột và load được model 3D thật — và
quan trọng không kém, chỉ ra những lỗi mà ai mới bắt đầu cũng vấp phải trong ngày đầu tiên.
Tôi đã làm WebGL và 3D trên các website thương mại từng đoạt giải quốc tế, và R3F là công cụ tôi chọn khi một scene cần sống bên trong một app React. Bắt tay vào dựng nào.
React Three Fiber là gì (và vì sao nên dùng)?
three.js là thư viện WebGL phổ biến nhất trên trình duyệt. Tự thân nó mang
tính mệnh lệnh (imperative): bạn phải new ra scene, camera, renderer, mesh, rồi tự tay điều khiển
vòng lặp animation. React Three Fiber (R3F) bọc tất cả lại trong React. Bạn mô tả scene bằng
JSX, R3F reconcile nó như mọi cây React khác, và bạn được dùng hooks, state cùng mô hình component
miễn phí.
Điểm mấu chốt: không có cú pháp đặc biệt nào phải học thuộc. Mỗi class của three.js trở thành một
phần tử JSX viết kiểu camelCase. new THREE.Mesh() thành <mesh>; new THREE.BoxGeometry() thành
<boxGeometry>. Tham số constructor ánh xạ vào prop args; mọi thuộc tính là một prop. Chỉ một quy
tắc đó đã bao trùm 90% những gì bạn sẽ viết.
R3F nằm trong hệ sinh thái pmndrs, cùng nhóm tạo ra Drei, Zustand và React Spring. Đây là một mảng anh em với GSAP và Lenis trong bài cuộn mượt trong Next.js với GSAP & Lenis — đều nhằm làm trình duyệt trở nên sống động, chỉ khác tầng trong stack.
Cài đặt và chiếc Canvas đầu tiên
Bạn cần React 19 cho bản major hiện tại. Cài renderer cùng Drei (thư viện helper gồm các component dựng sẵn như camera, controls, loader) và bản thân three.js:
npm install three @react-three/fiber @react-three/drei
# Người dùng TypeScript:
npm install -D @types/three
Phiên bản tại thời điểm viết:
@react-three/fiber@9đi cùngreact@19. Nếu bạn vẫn dùng React 18, hãy cài@react-three/fiber@8. Luôn kiểm tra peer dependency hiện hành trên trang npm trước khi bắt đầu.
Mọi thứ 3D đều nằm trong <Canvas>. Nó tạo WebGL renderer, một scene và một camera mặc định, đồng
thời tự chạy vòng lặp render cho bạn. Quan trọng: Canvas phải có kích thước — nó lấp đầy phần tử
cha, nên phần tử cha cần một chiều cao.
// Scene.tsx
import { Canvas } from "@react-three/fiber";
export default function Scene() {
return (
<div style={{ width: "100%", height: "100vh" }}>
<Canvas>
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="orange" />
</mesh>
</Canvas>
</div>
);
}
Bạn sẽ thấy... một ô vuông tối thui. Đúng rồi đấy, và đây là lỗi đầu tiên: meshStandardMaterial là
vật liệu dựa trên vật lý (PBR) nên cần ánh sáng. Không có nguồn sáng, nó render màu đen. Ta sẽ sửa
ngay sau đây — trước hết là cấu tạo.
Mesh, geometry và material
Một vật thể 3D nhìn thấy được là một mesh = geometry (hình dạng) + material (bề mặt). Trong R3F đó là ba phần tử lồng nhau:
<mesh position={[0, 0, 0]} rotation={[0, 0, 0]} scale={1}>
<boxGeometry args={[1, 1, 1]} /> {/* rộng, cao, sâu */}
<meshStandardMaterial color="orange" />
</mesh>
Ba điều cần khắc cốt:
argschính là chữ ký của constructor.<boxGeometry args={[1, 1, 1]} />đúng nghĩa lànew THREE.BoxGeometry(1, 1, 1). Thứ tự rất quan trọng; tra tài liệu three.js cho từng class.position,rotation,scalelà cách viết tắt cho transform của object. Chúng nhận mảng[x, y, z](rotation tính bằng radian, không phải độ).- Chọn material rất quan trọng.
meshBasicMaterialbỏ qua ánh sáng (hợp với phong cách phẳng, kiểu UI);meshStandardMaterialvàmeshPhysicalMaterialphản ứng với ánh sáng (chân thực). Người mới nên dùngmeshStandardMaterial.
Thử đổi <boxGeometry> thành <sphereGeometry args={[1, 32, 32]} /> hay
<torusKnotGeometry args={[1, 0.3, 128, 32]} /> để cảm nhận cách args chi phối hình dạng.
Vòng lặp render: useFrame
Scene tĩnh thì nhàm chán. Để animate, ta móc vào vòng lặp render bằng hook useFrame. Nó chạy
mỗi khung hình (lý tưởng là 60fps) và đưa cho bạn đồng hồ cùng trạng thái scene. Vì nó thay đổi
object three.js trực tiếp qua ref nên không kích hoạt re-render của React — đúng thứ bạn cần cho
chuyển động mượt 60fps.
import { useRef } from "react";
import { useFrame } from "@react-three/fiber";
import type { Mesh } from "three";
function SpinningBox() {
const ref = useRef<Mesh>(null!);
useFrame((state, delta) => {
// delta = số giây kể từ khung hình trước → độc lập với frame rate
ref.current.rotation.y += delta;
ref.current.rotation.x += delta * 0.5;
});
return (
<mesh ref={ref}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="orange" />
</mesh>
);
}
Lỗi lớn nhất của người mới ở đây: animate bằng giá trị cố định kiểu rotation.y += 0.01. Cách đó
buộc tốc độ vào frame rate, nên hộp sẽ xoay nhanh gấp đôi trên màn hình 120Hz. Luôn nhân với
delta để độc lập với frame rate — đúng nguyên tắc giúp các timeline của GSAP và Framer
Motion đồng nhất trên mọi thiết bị.
useFrame chỉ hoạt động bên trong một component được render dưới <Canvas>. Gọi nó bên ngoài, bạn
sẽ gặp lỗi "useFrame must be used within Canvas" — một cú vấp kinh điển khác.
Ánh sáng: vì sao scene của bạn đen thui
Vật liệu standard cần ánh sáng. Hãy thêm ít nhất một ambient light (ánh sáng nền phẳng) cùng một directional light (mặt trời, tạo highlight):
<Canvas>
<ambientLight intensity={0.4} />
<directionalLight position={[5, 5, 5]} intensity={2} />
<SpinningBox />
</Canvas>
Công thức ánh sáng gần như luôn nhìn ổn để khởi đầu:
| Loại đèn | Vai trò | Thiết lập điển hình |
|---|---|---|
ambientLight | Ánh sáng nền mềm, xóa vùng đen tuyệt đối | intensity={0.3–0.5} |
directionalLight | Đèn chính / mặt trời, tạo khối | lệch position, intensity={1–3} |
pointLight | Vầng sáng cục bộ (đèn bàn, lửa) | đặt gần vật thể |
<Environment /> (Drei) | Chiếu sáng theo ảnh, chân thực tức thì | preset="city" |
Để có kết quả nhanh và đẹp như ảnh chụp, <Environment preset="sunset" /> của Drei chiếu sáng cả
scene từ một ảnh HDRI chỉ trong một dòng — một mẹo tôi dùng liên tục.
OrbitControls: cho người dùng kéo camera
Khi prototype, bạn hầu như không muốn camera bị khóa cứng. Drei có sẵn OrbitControls, cho phép người dùng xoay, pan và zoom bằng chuột — thả vào là xong:
import { OrbitControls } from "@react-three/drei";
<Canvas camera={{ position: [3, 2, 5], fov: 50 }}>
<ambientLight intensity={0.4} />
<directionalLight position={[5, 5, 5]} intensity={2} />
<SpinningBox />
<OrbitControls enableDamping makeDefault />
</Canvas>
Đặt camera khởi đầu qua prop camera trên <Canvas> (fov là góc nhìn tính bằng độ).
enableDamping thêm quán tính để thao tác kéo có cảm giác đầm tay; makeDefault đăng ký bộ
controls này làm mặc định của scene để các helper Drei khác phối hợp ăn ý.
Load model 3D (glTF)
Hộp là để học; dự án thật thì load model. Định dạng chuẩn của web là glTF/GLB (cứ hình dung như
"JPEG cho 3D"). Hook useGLTF của Drei load nó và trả về scene graph đã được phân tích. Luôn bọc nội
dung bất đồng bộ trong <Suspense> để React hiển thị fallback trong lúc file đang tải:
import { Suspense } from "react";
import { useGLTF } from "@react-three/drei";
function Model() {
const { scene } = useGLTF("/models/duck.glb");
return <primitive object={scene} />;
}
export default function ModelScene() {
return (
<Canvas camera={{ position: [0, 1, 4] }}>
<ambientLight intensity={0.5} />
<directionalLight position={[5, 5, 5]} intensity={2} />
<Suspense fallback={null}>
<Model />
</Suspense>
<OrbitControls makeDefault />
</Canvas>
);
}
// Preload để model sẵn sàng trước khi component mount
useGLTF.preload("/models/duck.glb");
<primitive object={...} /> là "cửa thoát hiểm" để nhét một object three.js có sẵn vào cây JSX. Nếu
cần kiểm soát từng mesh chi tiết hơn, hãy chạy file GLB qua
gltfjsx — nó sinh ra một component có type với mọi node được bóc
tách, cực kỳ hữu ích khi bạn cần animate hay đổi màu từng bộ phận.
Các lỗi người mới hay gặp
Danh sách những lỗi tôi thấy nhiều nhất:
- Màn hình đen → thiếu ánh sáng, hoặc dùng
meshBasicMaterialmà lại mong nó phản ứng với sáng. - Không render gì → phần tử cha của Canvas không có chiều cao (co về 0px).
- Lỗi
useFrame/useLoader→ hook bị gọi bên ngoài<Canvas>. Mọi hook R3F phải chạy bên trong. - Tốc độ khác nhau theo màn hình → bạn quên nhân chuyển động với
delta. - Load model giật → thiếu boundary
<Suspense>, hoặc chưapreload. - Next.js App Router → render Canvas trong Client Component (
"use client"); WebGL không chạy được khi render phía server. - Bundle nặng / first paint chậm → để mắt tới hiệu năng từ sớm; three.js khá nặng. Cùng tính kỷ luật trong công việc front-end chú trọng hiệu năng áp dụng được ở đây.
Đi tiếp từ đâu
Bạn đã nắm vòng lặp cốt lõi: Canvas → mesh → useFrame → ánh sáng → controls → model. Từ đây, các bước tự nhiên tiếp theo là post-processing, shader GLSL tùy biến và 3D điều khiển theo scroll. Một bài thực hành nối tiếp rất hay là hiệu ứng displacement ảnh bằng WebGL, áp dụng chính những nguyên thủy này vào một hiệu ứng chuyển cảnh dựa trên shader mà bạn có thể đưa lên site thật.
Nếu 3D là một phần trong dự án lớn hơn — và bạn muốn một chuyên gia lo phần WebGL, hiệu năng và tích hợp — hãy xem dịch vụ creative development của tôi, hoặc đọc creative developer là gì.
Câu hỏi thường gặp
Có cần biết three.js trước khi học React Three Fiber không?
Không bắt buộc — nhưng có thì tốt. R3F ánh xạ một-một với các class của three.js, nên hiểu mesh,
material và camera về mặt khái niệm sẽ giúp các prop trở nên hiển nhiên. Bạn có thể học cả hai cùng
lúc; chỉ cần mở sẵn tài liệu three.js để tra args của từng class.
React Three Fiber đã đủ "chín" để chạy production chưa?
Rồi. Nó được dùng rộng rãi trên các site thương mại và từng đoạt giải, do nhóm pmndrs duy trì. Lưu ý chính là hiệu năng: WebGL ngốn GPU, nên hãy tính trước phần tối ưu (instancing, model nén draco, lazy load) khi scene lớn dần.
Dùng React Three Fiber với Next.js được không?
Được. Render <Canvas> bên trong một Client Component ("use client") — WebGL cần trình duyệt và
không chạy được khi render phía server. Cách phổ biến là lazy-load component 3D bằng next/dynamic
với { ssr: false }.
R3F và Drei khác nhau thế nào?
@react-three/fiber là renderer (cầu nối giữa React và three.js). @react-three/drei là bộ công cụ
tùy chọn gồm các helper dựng sẵn — OrbitControls, useGLTF, Environment, camera — giúp bạn khỏi phải
tự viết lại những phần thường gặp. Hầu hết dự án dùng cả hai.