# Three.js 網頁效果集

給**網站**用的效果，不是遊戲。

```
http://localhost/etung/three/web-fx/
```

## 設計原則

**每個效果是單一檔案、只依賴 `three`。** 沒有共用底層、沒有建置步驟。
要用哪個效果就複製 `effects/` 底下那一支到你的專案，改幾個參數就能跑。

那二十行的樣板（renderer 建立、resize、生命週期）在每支檔案裡都重複了一次。
這是刻意的——抽成共用模組會讓「複製一個檔案就能用」這件事變成「複製一個資料夾還要改 import 路徑」。

## 十五個效果

### 有主題的一批

命名統一在單字的東方美學下，每個都有明確的情境與情緒，不是散裝的技術展示。

| 主題 | 檔案 | 情境 | 情緒 · 配色 |
| --- | --- | --- | --- |
| **靜** | `aurora.js` | SaaS、金融形象站 hero | 沉穩遼闊 · 冷色藍綠紫 |
| **巡** | `infinite-gallery.js` | 作品集、商品分類頁 | 瀏覽的節奏 · 照片本色 |
| **境** | `terrain.js` | 科技產品頁、資料服務 | 理性冷冽 · 單色線條 |
| **熔** | `metal-blob.js` | 精品、香水、珠寶 | 高級稠密 · 金屬虹彩 |
| **引** | `magnetic.js` | 任何站的按鈕與連結 | 幾乎察覺不到 · 無色 |
| **裂** | `glitch.js` | 404、限時活動、品牌切換 | 緊張躁動 · RGB 偏移 |

`magnetic.js` 是唯一 **沒有 import three** 的一支——零 WebGL、壓縮後不到 2KB。
放在這組裡是刻意的：大特效吸睛，但真正讓網站質感提升的常常是這種微互動。

### 其餘

| 檔案 | 效果 | 成本 | 適合 |
| --- | --- | --- | --- |
| `fluid.js` | 游標流體 | 中 | hero 區。視覺衝擊最強的一個 |
| `particle-morph.js` | 粒子形變（支援中文） | 低 | 品牌關鍵字輪播、開場 |
| `holo-card.js` | 全像卡片 | 低 | **電商**：會員卡、限定商品、商品卡 |
| `gradient-flow.js` | 流動漸層背景 | 極低 | 任何頁面的底層 |
| `dust.js` | 塵埃粒子 + 游標排斥 | 低 | hero 區、空曠頁面 |
| `image-morph.js` | 圖片溶解轉場 | 低 | 輪播、作品集、商品主圖 |
| `image-ripple.js` | hover 液態扭曲 | 中 | 作品集縮圖、商品圖 |
| `glass.js` | 玻璃折射 | **高** | hero 主視覺 |
| `dissolve.js` | 溶解崩解 | 低 | 進場/離場、章節切換 |

全部共用同一組 API：

```js
const fx = createXxx(容器元素, { 參數 });
fx.dispose();          // 一定要呼叫，否則 WebGL context 會累積
```

## 三件每支都做了的事

這三件是網站專屬的，遊戲不用管：

**1　`prefers-reduced-motion`**
系統開了「減少動態效果」就停止動畫（停在一個好看的定格，不是變空白）。
這對前庭功能障礙的使用者是硬需求，不是可有可無的貼心。

**2　捲出畫面就停止算圖**
用 `IntersectionObserver` 判斷。背景效果最常見的浪費，就是使用者早就滑到頁尾了它還在燒 GPU。

**3　高 DPI 螢幕的像素上限**
`Math.min(devicePixelRatio, 1.5~2)`。全螢幕 shader 的成本跟像素數成正比，
Retina 全開等於多燒四倍。

展示頁本身還多做一件：**只有捲到附近的效果才會被建立，離開就 `dispose()`**。
瀏覽器同時能開的 WebGL context 只有十幾個，一次全開的展示頁在筆電上會直接發燙。

## 用在正式網站前

1. **文字內容留在 DOM，不要畫進 canvas** —— 否則 SEO 歸零、螢幕閱讀器也讀不到
2. **手機要降級** —— 全螢幕 shader 在手機上跑滿 60fps 會很快掉電。
   `dust.js` 已經會在 `pointer: coarse` 時把粒子數砍半，其餘的請自行判斷要不要關掉
3. **首頁別塞大模型** —— 3D 資產拖慢首屏，轉換率會掉。
   通常「輕量 shader 背景 + 一個重點互動」比整站都在動有效得多

## 幾個實作上的細節

**cover 裁切**（`image-morph.js` / `image-ripple.js`）
圖片長寬比幾乎不會等於容器。直接把貼圖貼上去會被拉扁，所以 shader 裡有
`coverUv()` 複製 CSS `object-fit: cover` 的行為。這是圖片類效果最常被忽略的一步。

**色彩空間**
貼圖沒設 `colorSpace = THREE.SRGBColorSpace` 的話，three 會把它當線性資料，
整張圖會泛白。這個錯誤很常見而且不容易一眼看出來。

**漸層的色帶**
大面積漸層在 8-bit 螢幕上一定會出現色帶。`gradient-flow.js` 在最後加了
半個色階的隨機雜訊把色帶打散——肉眼看不到雜訊，但看得到色帶消失。

**溶解不要自己寫材質**
`dissolve.js` 用 `onBeforeCompile` 去改 `MeshStandardMaterial`，
保留 three 完整的光照、陰影、環境貼圖與色調映射。
從零寫 `ShaderMaterial` 的話那些全部要重刻。

**流體是簡化版的，這點要講清楚**
真正的 Navier-Stokes 還要解壓力場（divergence → 多次 Jacobi 迭代 → 減去梯度）
才能保證不可壓縮，那要再多三支 shader、每幀多二十幾次繪製。
`fluid.js` 省掉壓力步驟，改用渦度強化把旋渦找回來——物理上不嚴謹，
視覺上有九成像，成本只有四分之一。網頁背景要的是好看不是正確。

**粒子取樣是走 canvas 的**
`particle-morph.js` 把文字或圖畫到 2D canvas，讀回像素，挑不透明的點當目標位置。
所以**任何能畫在 canvas 上的東西都能變成粒子**——中文字、logo、PNG、SVG 全部通吃，
不需要事先準備點位資料。

**全像卡片為什麼不用純 CSS**
CSS 也能做傾斜跟漸層，但虹彩必須跟著實際視角走才有金屬箔的感覺。
那需要在 fragment shader 裡拿到每個像素的視線方向——CSS 只能整片一起變色，
一眼就看得出是貼上去的。

**玻璃要三個條件同時成立**
環境貼圖（否則沒有反射，是一片死灰）、後方要有東西（純色背景折射了也看不出來）、
`ior` 與 `thickness` 要一起調（只調透明度不會變成玻璃）。

## 素材

示範照片來自 [Unsplash](https://unsplash.com)（Unsplash License，可商用），
已縮到 1200px 寬並壓縮，六張共 0.8 MB。正式專案請換成自己的圖。

## 還沒做的

原本規劃但這一輪沒做的效果：極光波浪、圖片牆視差、遮罩揭露、
捲動驅動的模型組裝、磁吸按鈕、文字碎裂。
要哪一個跟我說。
