3D 作品集
建立一個只能屬於這個人的單頁式作品集。這個技能所產生的網站是以一個 DJ 的唱片箱為核心:每個專案都是一張你可以翻閱的唱片封套,而當唱片在前方時,它會旋轉。範例:https://alessio-simoya.vercel.app
請不要複製那個概念。遵循這個流程,並找到屬於他們的獨特概念。
1. 首先收集輸入資料
在開始設計之前先詢問這些資訊。從一開始就用真實內容來建構。
- 專案: 對於每個專案,提供一份簡短的事實摘要:它是什麼、你的角色、日期、成果、使用的技術棧、一個線上連結,以及一份「不得聲稱」的清單。
- 經歷: 公司、職稱、日期、地點,以及每份工作的一項具體成果。
- 技能: 分組列出,並說明每項技能在哪裡應用過。
- 工作之外的生活: 嗜好、參與的團隊、擅長的料理、去過的地方。這是創意概念的來源,所以問得比他們願意提供的還要多。
- 素材: 標誌、產品截圖、影片、Rive 或 Lottie 檔案。詢問專案資料夾的位置並搜尋它們。
- 連結: LinkedIn、GitHub 等。除非對方明確同意,否則絕不公開他們的電子郵件地址。
2. 找到一個獨特概念
選取此人世界中最具代表性的事物,並將其作為主視覺,製作成一個可以讓使用者互動的真實 3D 物體。DJ 會有一個唱片箱。攀岩者可能會有一面岩點牆;麵包師傅則可能有一個麵包托盤。每個專案都變成這個物體中的一個項目。
把大膽的創意用在這裡。頁面的其餘部分保持簡潔:純色色塊、一個具有個性的顯示字體、一個樸素的正文字體、四到五種命名顏色。
3. 技術棧
- Vite、React 和 TypeScript(嚴格模式)
- three.js 用於 3D 渲染,直接撰寫(不需要場景框架)
- 僅在對方已有
.riv檔案時才使用 Rive;並延遲載入其 wasm - tRPC 和 zod 用於 API,作為 Vercel Function 位於
api/trpc/[trpc].ts - Resend 用於電子郵件,Cloudflare Turnstile 用於防垃圾郵件,Neon Postgres 用於資料記錄
- 透過
@fontsource-variable/*自託管可變字型
4. 3D 的組合方式
使用一個固定的全螢幕 WebGL canvas,設定 pointer-events: none,並將每個 3D 視圖繪製到 DOM 佔位符的矩形區域中:
const r = el.getBoundingClientRect();
renderer.setViewport(r.left, innerHeight - r.bottom, r.width, r.height);
renderer.setScissor(r.left, innerHeight - r.bottom, r.width, r.height);
renderer.render(scene, camera);
每影格清除一次,開啟裁剪測試,並跳過任何在畫面外的元素。這樣做可以只花費一個 context 的資源,就在頁面下方呈現多個 3D 物體,而且它們會隨著版面滾動。
為每個小物體建立一個「骨架」:一個群組、一個攝影機位置、一個注視點,以及一個由共同時鐘驅動的 tick(t) 函式。懶散地建立骨架,並一次只顯示一個。在 2D canvas 上繪製紋理(標誌、標籤、圖案),這樣就不需要載入模型檔案。
過去曾發生的錯誤以及如何避免:
- 比元素本身還大的地面陰影平面會顯示為一個硬邊矩形。請將其大小設定為適合在視圖內。
- 當你需要「繞著自身軸心旋轉」時,先旋轉一個群組,再旋轉其子物件。
- Sprite 和透明平面會在實心網格之後繪製;物體下方的光暈應該是一個位於其下方的平面,而不是一個面向攝影機的 sprite。
5. 效果良好的區塊
- 主視覺區: 姓名很大,一個會循環顯示其角色的貼紙,一句話的介紹,以及可互動的物體。背後一個彩色圓盤會根據焦點項目變化。
- 作品區: 每個案例研究以該品牌顏色的色帶打開,帶有真實標誌,然後是一個橫向的圖片、影片和動畫捲軸,三個重點摘要,幾個重要數字,以及技術棧標籤。長篇文字放在「閱讀完整故事」後面。
- 經歷區: 每份工作一行,並附上一項重要數字。
- 旅程區: 學校或里程碑,每個都有其自己的動畫物體。
- 技能區: 移動的文字行,而非表格。
- 下班時間區: 每個興趣一個動畫物體。
- 旅行區: 地點像冰箱磁鐵一樣貼在鋼門上,可以拖曳。
- 打招呼區: 聯絡表單。
一個浮動的藥丸狀頁首會使用 IntersectionObserver 標記當前區塊。
6. 基本規則
- 僅陳述事實。 使用對方提供的資訊。如果某個區塊缺少對方未提供的內容,就省略它。將每個專案的「不得聲稱」清單以註解形式保留在內容檔案中。
- 不虛榮計數。 提交次數、程式碼行數、頁面數量或已讀取模型數量會顯得不成熟。展示產品及其成果。
- 文字要精簡。 如果一個畫面大部分是段落,請將其轉換為視覺化內容。
- 尊重他人作品。 不要從預覽中重建付費模板,也不要複製某個品牌的產品設計(例如,一個具名的比賽用球);以相同的精神繪製原創作品。只使用自由授權的圖片,並在頁面上標示來源。
- 螢幕截圖 不得顯示真實客戶的名字、金額或面孔。
- 動畫要有關閉開關。 尊重
prefers-reduced-motion,加入一個「暫停動畫」按鈕,並在 WebGL 不可用時提供一個靜態的備用方案。 - 將所有內容放在一個型別檔案中(
src/content/portfolio.ts),以便使用者可以在不接觸元件的情況下編輯文字。
7. 聯絡表單
一個 tRPC 程序,contact.send,使用 zod 進行輸入驗證。依序執行以下動作:
- 如果隱藏的蜜罐欄位已填寫,則默默地回傳成功;
- 對每個地址施加一個小的速率限制;
- 使用 Cloudflare 驗證 Turnstile 令牌,如果驗證失敗則拒絕;
- 透過 Resend 發送電子郵件給網站擁有者,並將寄件者設為
reply_to; - 將每條訊息儲存到
messages資料表中; - 只有在訪客勾選了一個預設未勾選的「通知我」核取方塊時,才將他們儲存到
subscribers資料表(包含同意文字和時間)以及 Resend 的受眾清單中。
未經對方勾選,絕不將任何人加入郵件清單。
環境變數:RESEND_API_KEY、ENQUIRY_TO_EMAIL、ENQUIRY_FROM_EMAIL、RESEND_AUDIENCE_ID、DATABASE_URL、TURNSTILE_SECRET,以及給瀏覽器使用的公開網站金鑰。
過去曾發生的錯誤:
from的值可能已經是Name <address>格式。再次對其進行包裝會導致 Resend 回應 422。當它包含<時,請直接使用該值。- Vite 只會暴露以
envPrefix中定義的前綴開頭的變數。如果網站金鑰是以其他名稱傳入,請新增該前綴。 - Turnstile 小工具只能在 Cloudflare 中為其設定的主機名稱上運作。
- 在正式環境中,如果沒有 Turnstile 密鑰,則拒絕發送。僅在本地開發環境中使用 Cloudflare 發布的測試金鑰。
- 加入一個
CONTACT_DRY_RUN=1開關,在正式環境中忽略,以便可以在不發送電子郵件的情況下測試整個流程。 - 為了在
vite dev下執行相同的處理程式,加入一個小的外掛程式,該外掛程式使用server.ssrLoadModule載入函式,並將Request物件傳遞給它。 - 建立包含表格和行內樣式的電子郵件 HTML,並跳脫每個欄位。
8. 邊做邊檢查
每次進行修改後:
- 執行建置;
- 使用 Playwright 在 1440 和 390 像素寬度下為每個區塊截圖(啟動 Chromium 時加上
--use-gl=swiftshader參數,以便 WebGL 能在無頭模式下渲染); - 確認
document.documentElement.scrollWidth - innerWidth為 0; - 在你說「完成」之前,自己先檢查截圖。
明確說明未檢查的項目:真實手機、Safari 瀏覽器、動畫的流暢度、真實的郵件傳遞。
9. 部署
git init,並在.gitignore中加入.env*和.vercel。在第一次提交前掃描暫存檔案中是否有金鑰。vercel link,新增環境變數,vercel --prod。- 檢查線上頁面是否正常載入沒有失敗請求,以及 API 是否拒絕了沒有 Turnstile 令牌的請求。
- 要求網站擁有者透過線上表單發送一封真實的訊息進行測試。
起始提示
給對方填寫以下內容:
使用 3d-portfolio 技能建立我的 3D 作品集。
姓名和一行自我介紹:
要在主視覺區循環顯示的角色(4 到 6 個):
專案(針對每個專案:事實、我的角色、日期、連結、不得聲稱的內容):
經歷(公司、職稱、日期、每份工作的一項具體成果):
技能(分組):
學校或里程碑:
工作以外的興趣(請具體說明):
我去過的地方:
我的標誌、截圖和動畫檔案在此電腦上的位置:
要顯示的連結(LinkedIn、GitHub 等):
我永遠不想在網站上看到的東西: