with-canvas
Canvas 工作通常會失敗於兩種不同的方式:視覺程式碼雖然好看但卡頓,或是經過最佳化但難以維護或缺乏無障礙性。在撰寫繪圖呼叫之前,應先將每項任務視為渲染架構決策。僅載入當前階段所需參考文件,不要預先載入所有參考文件。
階段 1 - 分類 Canvas 工作
在選擇 API 之前,先辨識主要的工作負載類型:
- 拒絕使用 Canvas: 當場景主要是文字、無障礙控制項、文件排版、簡單圖示、CSS 動畫、媒體播放或少量的可縮放向量形狀時,優先使用 DOM/CSS/SVG/video。
- 裝飾性動畫: 非互動式背景氛圍、粒子效果、星星、鳥群、程序化循環。
- 資料渲染: 圖表、地圖、熱力圖、大量點雲、頻繁更新的量測數據。
- 生成式藝術: 基於種子值/隨機繪圖、創意草稿、程序化系統、可匯出的圖像。
- 互動工具: 滑鼠/鍵盤輸入、選取、拖放、可編輯物件、碰撞檢測。
- 遊戲/模擬: 固定更新循環、動態實體、碰撞/物理、分層場景。
- WebGL/引擎 Canvas: Three.js/Babylon.js/其他擁有 GPU 資源的渲染器。
強制閱讀: 如果工作負載是裝飾性動畫或生成式藝術,請閱讀 references/visual-quality.md —— 對這兩類任務而言,好看是目標,僅靠效能/無障礙衛生習慣只能產出一般品質的輸出。請勿為資料渲染、工具、遊戲或引擎 Canvas 載入此文件。
退出條件:說明工作負載類型、為何 Canvas 是正確的原始方案、目標幀率、預期物件數量、互動模式,以及 Canvas 是裝飾性還是語義上有意義。
階段 2 - 選擇渲染模型
當場景主要是立即模式繪圖、畫素操作、精靈圖、簡單動畫或套件大小很重要時,使用原始 Canvas 2D。
當應用程式需要可編輯物件、選取、拖放、分層場景圖、事件委派或序列化時,使用保留模式函式庫。
使用 p5.js 處理草稿、生成式藝術、教學/原型開發或創意程式設計,當 setup/draw 的易用性比框架整合更重要時。
使用 Paper.js 處理向量編輯工作流程:路徑、區段、貝茲曲線幾何、碰撞檢測、SVG 匯入/匯出、分層向量文件。
使用 Konva 處理 Canvas 上的應用程式 UI:與 React/Vue/Svelte 整合、可拖曳形狀、圖層、快取形狀、事件監聽控制項。
當其元件模型符合需求且專案能接受依賴的新鮮度時,使用 Pencil.js 處理輕量場景/元件抽象。
當主要成本在於 3D/GPU 渲染、著色器、攝影機、材質或大量網格/精靈圖工作負載時,使用 Three.js/Babylon.js 或直接使用 WebGL。
強制閱讀: 如果正在選擇函式庫或審查函式庫選擇,請閱讀 references/library-selection.md。除非任務進入該階段,否則請勿載入效能或審查參考文件。
在安裝 Canvas 函式庫或撰寫非平凡的函式庫 API 程式碼之前,請在使用 Context7 時先驗證當前文件;否則請使用官方文件。請勿依賴記憶中的 p5.js、Paper.js、Pencil.js、Konva、ZIM、Three.js 或 Babylon.js API。
退出條件:說明選擇的模型以及被拒絕的替代方案,並附上一行原因。
階段 3 - 設計循環與狀態
將 Canvas 程式碼分為生命週期、狀態更新、渲染、輸入、調整大小和清理。對於框架元件,將命令式 Canvas 引擎保持在單一的 mount/init 邊界後面。
對可見動畫使用 requestAnimationFrame。當模擬速度需要在不同更新率下穩定時,從 RAF 時間戳記追蹤 delta。在清理時取消 RAF,在隱藏時暫停或減少工作量,並尊重 prefers-reduced-motion。
選擇以下其中一種重繪策略:
- 完全重繪:適用於簡單/裝飾性場景,簡單為上。
- 髒矩形:移動中的物件,邊界變化小且背景穩定。
- 分層 Canvas:靜態/慢速背景、動態實體、UI/互動疊加層。
- 精靈圖/快取貼圖:重複的昂貴形狀、文字、標記、縮放後的圖像、粒子。
- Worker OffscreenCanvas:大量繪圖或模擬,與輸入/主執行緒競爭。
強制閱讀: 在最佳化動畫、大型場景、粒子、圖表、圖像縮放、高 DPI 輸出或 Worker 渲染之前,請閱讀 references/performance.md。除非要新增/替換函式庫,否則請勿載入 library-selection。
退出條件:依序撰寫影格管線:輸入取樣 -> 更新 -> 清除/重繪策略 -> 繪圖 -> 排程下一個影格。
階段 4 - 實現 Canvas 衛生習慣
使用備份儲存區而非僅以 CSS 設定 Canvas 大小。為獲得清晰輸出,分別設定 CSS 大小,再設定 canvas.width/height = cssSize * devicePixelRatio,然後使用 ctx.setTransform(dpr, 0, 0, dpr, 0, 0) 正規化變換。對於效能需求高的場景,可故意降低 DPR。
使用符合場景的選項初始化 context。對不透明 Canvas 使用 { alpha: false }。僅在頻繁進行 getImageData 讀取時使用 willReadFrequently: true,因為它可能改變備份策略。
對於輸入,透過 getBoundingClientRect() 和 Canvas 縮放比例轉換指標座標。不要相信 clientX/clientY 就是 Canvas 座標。
對於無障礙性,裝飾性 Canvas 應設定 aria-hidden="true" 且不帶互動處理器。有意義的 Canvas 互動需要 DOM 控制項、鍵盤路徑、焦點管理、標籤或等效的非 Canvas 表示方式。
退出條件:調整大小、DPR、context 選項、事件座標對應、動畫偏好和清理都必須明確處理。
階段 5 - 審查與驗證
將 Canvas 程式碼視為熱門循環加上資源生命週期來審查。不要只停留在視覺正確性。
強制閱讀: 在審查現有 Canvas 程式碼或完成新實作之前,請閱讀 references/review-checklist.md。除非審查包含依賴項的適用性,否則請勿載入 library-selection。
使用至少一個適合該任務的執行時訊號進行驗證:Chrome 效能面板、FPS 計數器、記憶體快照、互動延遲、減少動畫路徑、隱藏分頁行為或低階/行動裝置節流。
範例
- 使用者要求「製作粒子背景」:分類為裝飾性動畫,只有在粒子數量或程序化繪圖證明 Canvas 合理時才拒絕 DOM/SVG,然後閱讀
references/visual-quality.md了解外觀,以及references/performance.md了解循環。 - 使用者要求「建立帶選取功能的可拖曳形狀」:分類為互動工具,閱讀
references/library-selection.md,根據物件模型可能選擇 Konva/Paper.js/原始 Canvas 2D。 - 使用者要求「審查這個 Canvas 循環」:分類現有工作負載,閱讀
references/review-checklist.md,僅在出現卡頓、大量物件或熱門路徑問題時才閱讀references/performance.md。 - 使用者要求「加入 p5 草稿」:閱讀
references/library-selection.md,在撰寫 API 程式碼前先驗證當前 p5 文件,然後保持框架生命週期邊界明確。
絕不要
-
絕不要僅以 CSS 調整已繪製 Canvas 的大小 反之: 故意設定備份儲存區尺寸和 CSS 尺寸;在調整大小後套用一次 DPR 變換。 原因: 僅 CSS 縮放會使輸出模糊,並隱藏備份儲存區不匹配的問題,這在 Retina 顯示器上會更嚴重。
-
絕不要在沒有清理路徑的情況下啟動 RAF 循環 反之: 儲存影格 ID,在卸載/銷毀時取消,並防止在可見性或媒體查詢變更後重複啟動。 原因: 孤兒循環會持續繪製已分離的 Canvas,產生記憶體/CPU 洩漏,而快照通常會錯誤地將其歸因於瀏覽器內部。
-
絕不要因為昂貴的靜態內容視覺上屬於場景一部分而每幀都重繪 反之: 將其快取在離屏 Canvas、圖像點陣圖、圖層 Canvas、Path2D 或函式庫形狀快取中。 原因: Canvas 是立即模式;未更改的視覺內容仍需完整的繪圖時間,除非明確快取或分層。
-
絕不要將僅限 Canvas 的互動作為唯一的無障礙控制方式 反之: 提供 DOM 控制項/語義,或一個可透過鍵盤操作的保留物件模型,並附帶標籤和焦點行為。 原因: Canvas 畫素不會向輔助技術暴露角色、名稱、狀態或點擊目標。
-
絕不要僅僅因為效能不佳就將 Canvas 工作移至 OffscreenCanvas 反之: 首先辨識瓶頸是繪圖呼叫、狀態變動、過度繪製、圖像縮放、畫素讀取還是模擬;只有在主執行緒競爭是問題時才使用 Worker。 原因: Worker 增加了傳輸、打包和 DOM 存取限制;它們無法修復不良的繪圖架構。
-
絕不要忽視保留模式函式庫的效能規則 反之: 在惰性圖層上停用監聽、快取昂貴形狀、盡量減少圖層數量,並透過函式庫生命週期銷毀節點/舞台。 原因: 函式庫增加了場景圖和事件機制;忽略其開關可能比原始 Canvas 更慢。