# 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.