# Thiết kế: Tối ưu hiệu năng web phy-vr360

- **Ngày:** 2026-06-25
- **Trạng thái:** Đã duyệt thiết kế, chờ lập kế hoạch triển khai
- **Chiến lược:** Hướng A — tối ưu theo giai đoạn, ưu tiên việc rẻ–lợi-cao
- **Ràng buộc người dùng:** Giữ thẩm mỹ tổng thể nhưng bỏ lớp thừa; được phép nén cả ảnh và model 3D.

---

## 1. Vấn đề

Web khi chạy làm laptop nóng/quạt rú, cuộn giật, trải nghiệm không mượt. Cảm nhận là **toàn bộ site đều nặng**, không khu trú một chỗ.

## 2. Nguyên nhân gốc (đã phân tích)

### Nghiêm trọng
1. **Trang chủ chồng 3 ngữ cảnh WebGL chạy liên tục** — `hero-shader-background.tsx`:
   - 2× `MeshGradient` (shader GPU paper-design) animation vô hạn 60fps.
   - 1× `HeroPanoramaBackground` (`hero-panorama-background.tsx`): WebGLRenderer three.js, quả cầu xoay, `requestAnimationFrame` render **vô điều kiện**, tải ảnh panorama vài MB chỉ để làm nền mờ 58%.
   - `mix-blend-mode: soft-light` + filter SVG `feTurbulence`.
   - Không có `IntersectionObserver`/`visibilitychange` → chạy cả khi cuộn khỏi vùng nhìn hoặc đổi tab.
2. **Ảnh panorama nặng, không tối ưu** — 26 ảnh JPG **6144×3072**, 2–4MB/ảnh (tổng ~70MB), nạp thẳng làm texture three.js ở độ phân giải gốc. Cache `panoramaTextureCache` (`VirtualTour.tsx:214`) giữ **toàn bộ** texture vĩnh viễn → tràn VRAM trên máy card tích hợp → thrashing/khựng. `next.config.ts` rỗng.
3. **Model 3D `.glb` khổng lồ** — `chuong-2.glb` 40MB, `chuong-1.glb` 27MB → đơ tab khi parse. `AntiquityViewer.tsx` render mọi frame vô điều kiện, pixelRatio tới 2.

### Phụ (tích lũy)
4. **CSS animation luôn-bật + hiệu ứng nặng** (`globals.css`): nền `body` chồng 4 lớp gradient + `body::after` `mix-blend-mode: multiply` full màn hình; `public-ambient-pan` 18s vô hạn; gradient-text flow; logo/CTA "breathe"; header "glint"; lớp `.site-grain` nhiễu SVG full màn hình; **`backdrop-filter: blur(18px)`** trên mọi `.public-panel`/`.public-card` + `will-change: transform` (ép tạo layer, tốn bộ nhớ).
5. **Điểm tốt sẵn có:** Tour viewer chính (`VirtualTour.tsx:1502`) đã render **theo nhu cầu** (`needsRenderRef`) — giữ và nhân rộng mô hình này.

## 3. Phạm vi

**Trong phạm vi:** trang chủ + các trang công khai, tour viewer 360, viewer cổ vật 3D, pipeline tài nguyên (ảnh + model), cấu hình build, ngân sách CSS.

**Ngoài phạm vi:** thay đổi nội dung/bố cục lớn, thêm tính năng mới, thiết kế lại giao diện.

## 4. Giải pháp theo giai đoạn

### Giai đoạn 1 — Chặn việc chạy nền vô ích *(đòn bẩy lớn nhất, rủi ro thấp)*
- **Hero:** thay `HeroPanoramaBackground` (canvas three.js xoay) bằng **ảnh tĩnh đã tối ưu** (next/image hoặc CSS background) → loại bỏ 1 ngữ cảnh WebGL + texture vài MB. Giảm **2 → 1** `MeshGradient`; bỏ `mix-blend-mode: soft-light`; gỡ filter SVG `feTurbulence` nếu không cần.
- **Gate mọi WebGL/animation nền** bằng `IntersectionObserver` + `document.visibilitychange`: ngoài màn hình hoặc đổi tab → **dừng `requestAnimationFrame`/`setAnimationLoop`**. Áp dụng cho hero (nếu còn shader) và `AntiquityViewer`.
- **AntiquityViewer:** chuyển sang **render theo nhu cầu** (theo mô hình tour viewer) — chỉ vẽ khi `controls` đổi hoặc đang tự xoay; pixelRatio tối đa **1.5**; dừng khi ngoài màn hình; đảm bảo chỉ **một** viewer hoạt động tại một thời điểm (lazy-mount, không khởi tạo tất cả `.glb` cùng lúc).

### Giai đoạn 2 — Pipeline tài nguyên
- **Ảnh panorama:** thêm script Node (`sharp`) tạo bản tối ưu:
  - Resize tối đa **4096px** chiều rộng (giữ tỉ lệ 2:1), xuất **WebP** chất lượng ~80 (cân nhắc AVIF). Mục tiêu giảm 70–85% dung lượng.
  - Tour viewer nạp bản `.webp`; giữ LQIP blur-up sẵn có (`chua-thai-lac.lqip.ts`).
- **Model 3D:** dùng `gltf-transform` (Draco/meshopt + nén texture/resize) → 40MB ⟶ ước tính 3–8MB. Thêm `DRACOLoader`/meshopt decoder vào `AntiquityViewer`; lazy-load model.
- **Cache texture có giới hạn (LRU):** chỉ giữ cảnh hiện tại + lân cận, dispose phần xa → tránh tràn VRAM. Cân nhắc tắt mipmap cho ảnh cầu (giảm chi phí upload), kiểm chứng chất lượng.

### Giai đoạn 3 — Ngân sách hiệu ứng CSS
- Giảm **`backdrop-filter: blur`**: hạ bán kính hoặc thay bằng nền bán trong suốt cho phần tử phổ biến; **bỏ `will-change: transform`** ở phần tử luôn hiển thị (chỉ đặt khi cần, ví dụ lúc hover).
- Tắt bớt animation **vô hạn** ít giá trị: `public-ambient-pan`, gradient-text flow, logo/CTA "breathe", header glint, `.site-grain`. **Giữ** hiệu ứng xuất hiện-khi-cuộn (chạy 1 lần) và marquee.
- Gọn lại lớp gradient nền `body`/`public-page`.

### Giai đoạn 4 — Chia nhỏ bundle & tải
- `dynamic import` các thành phần nặng three.js (tour viewer, `AntiquityViewer`) để bundle chính nhẹ; đảm bảo `framer-motion`/`lottie-react` chỉ tải nơi cần.
- Cấu hình `next.config.ts`: bật `images` (AVIF/WebP, `deviceSizes`) và tối ưu build hợp lệ cho Next 16.

## 5. Cách đo thành công (nghiệm thu)
- **Trang chủ lúc rảnh ≈ 0% GPU** (không còn rAF chạy ngầm); cuộn ~60fps; không còn quạt rú.
- Số ngữ cảnh WebGL trên trang chủ: **3 → 0–1**.
- Dung lượng tải trang giảm rõ (ảnh panorama & model 3D).
- Lighthouse Performance tăng; không hồi quy giao diện nhìn thấy được.

## 6. Rủi ro & lưu ý
- **Next.js 16 bản đã chỉnh sửa** (theo `AGENTS.md`): trước khi sửa `next.config`/dynamic-import phải đọc tài liệu trong `node_modules/next/dist/docs/`.
- Nén ảnh/model có thể đổi nhẹ chất lượng → kiểm chứng bằng mắt sau mỗi bước; giữ file gốc (không ghi đè, xuất ra thư mục/định dạng mới).
- Việc gate animation phải dọn dẹp listener đúng cách để tránh rò rỉ bộ nhớ.

## 7. Nguyên tắc thực thi
- Làm tuần tự theo giai đoạn; **đo lại sau mỗi giai đoạn** trước khi sang bước kế tiếp.
- Mỗi thay đổi giữ đúng pattern/định dạng code hiện có của dự án.
