← Back to list

React.js + Canvas + Konva.js:打造專屬的網頁小畫家

這篇文章想記錄團隊在收到這個類「小畫家」需求時的抉擇與實踐。為什麼使用套件而不是直接使用原生 Canvas API 呢? 搭配著程式碼與數學,介紹 React.js 如何結合 Konva.js 來操作 Canvas。

Jerry Yang in JoinX 哲煜科技 · 2024-07-05 09:15 · 10 claps · 7.0 min read
#react #canvas #konva #nextjs #technical-notes
Open on Medium ↗
Wiki topics: TLS · Design Tools & Workflow 🌐 · Web Development

React.js + Canvas + Konva.js:打造專屬的網頁小畫家

前言

如果想要在網頁上實作「小畫家」基本工具,像是鉛筆、鋼筆、矩形、橢圓、橡皮擦和魔術棒🤯,結合旋轉、拖曳、節點編輯、縮放、調整亮度、調整對比度等功能標記輪廓在載入的圖片上,最後導出對應的座標、寬高、角度、亮度、顏色、透明度等資訊給後端儲存並訓練 AI 模型,你會選擇一肩扛起原生的 Canvas API 逐步鑄造屬於自己的工具庫呢?亦或者踏破鐵鞋找尋符合需求情境的開源套件呢?

這篇文章想記錄下,我們團隊在接到這個有趣的類「小畫家」需求時的抉擇與實踐。

為什麼使用套件而不是直接使用原生 Canvas API 呢?

搭配簡單的程式碼與數學,介紹 React.js 如何結合 Konva.js 操作 Canvas。

Let’s Go~

DOM Elements vs Canvas API

DOM 元素在網頁上建立後,能在畫面上清楚地呈現,瀏覽器還幫我們把元素的樹狀資料結構在 Devtool 中描繪出來,整體配合 JavaScript、CSS 相當便利,隨時可以用document.querySelector 選取並改動它的屬性,或是新增事件監聽器(addEventListener())加入互動要素,也可以用 CSS Selector 選中元素並為其改變樣式,開發體驗良好,技術也相當成熟。

相較之下 Canvas API 又是如何呢?Canvas API 是由瀏覽器實作,能通過 JavaScript 指令式操控畫布(canvas)中的像素(pixels),讓我們利用它提供的 API 函式繪製 2D 圖形,儘管有豐富的 API,但仍然有不少缺點:

  • 沒有實例 不存在父元素、子元素的概念,繪製完成後就成為一個個像素罷了
  • 沒有選取器 既然無法定義為「個體」,自然無法選擇
  • 沒有互動性 無法選擇代表不能新增事件監聽器(點擊、移動、拖曳等等)

總結來說:Canvas 就像現實中的畫布,一但揮灑顏料之後就是不可逆了,沒辦法編輯或刪除所畫的筆墨,只能再用新的色彩疊加上去,或乾脆換一張新的畫布(CanvasRenderingContext2D.clearRect()),上述總總對於已經習慣與 DOM 元素打交道的前端來說是挺困擾的。

先拋開缺點,來說說它的優點吧!因得益於瀏覽器對硬體加速的支援(尤其是 GPU 加速),Canvas 的效能非常優秀,GPU 的平行計算能快速處理複雜的圖形、動畫和大量像素操作,然後,好像就沒有了……

以效能來看, Canvas 的地位無可取代,換而言之只要能補上以上缺失,那麼 Canvas API 將會極為強大!

挑選合適的套件

必要

Canvas API 有關的套件很多元,考慮到專案的需求情境 -「圖片的標記與儲存」,首先要挑選具備基本工具 API(鉛筆、矩形、橢圓、旋轉、拖曳、縮放等)且擴充性佳的套件,還要能以 object 模型描述「圖片」及「輪廓」的資訊(座標、寬高、角度、亮度、顏色、透明度等),方便儲存和復原的格式。

希望有

因為專案的架構是 Next.js,因此會希望和 React.js 有較高的整合度,最好能封裝成元件(Component)的形式,可透過傳入 props 調整設定,以及和 React.js Hooks API 有不錯的相性。

整理下來有兩個套件較符合(2024/07/03 npm 資料):

  • **Fabric.js** — Star: 28K — Issues: 351 — Weekly Downloads: 171K — Unpacked Size: 23.8MB

  • **Konva.js** — Star: 11K — Issues: 35 — Weekly Downloads: 309K — Unpacked Size: 1.41MB

表格整理:

[embed]

總結

  • 選擇 Fabric.js。如果專案需要強大的互動性和圖形編輯功能,並且對效能要求不是特別苛求。
  • 選擇 Konva.js。如果專案需要處理大量圖形物件和複雜的動畫效果,並且對效能有較高要求,尤其是當你的應用基於 React 時。

最後我們決定用 Konva.js,主要是不想集中在 React.js useEffect 去操作套件 API,而且 Konva.js 的文件跟 demo 寫得很詳細,API 設計上簡潔、一致,而且是用 TypeScript 撰寫,開發體驗相當友善,效能面也更為優秀。

Konva.js 和 Next.js

Konva.js 基本架構

  • Stage 可視為 Viewport,是整個應用的根容器,外層會建立一個 div 容器,決定畫布大小。管理多個子層 Layer,能處理大部分互動事件。
  • Layer 是 Stage 的子層,用來分隔不同的圖層,每個 Layer 有兩個 <canvas>
  1. Scene renderer,繪製我們所看到的內容。
  2. Hit graph renderer,高效能地監聽事件(此 renderer 是看不到的)
  • Group 可包含多個 Shape,用來組合和統一管理底下相關的圖形。
  • Shape 指 Text、Rect、Circle 和 Ellipse 等等,是封裝過的圖形類別

[embed]

在 Next.js 中使用

我們專案的 Next.js 版本是 14,使用 App Router,如果直接在 page.tsx 引用上面的元件會報錯:

Module not found: Can’t resolve ‘canvas’

因為 react-konva 設計是在 Client-Side 執行,所以在 Next.js 預設為 Server-Side 的環境會有問題。

參考 Konva 官方提供的解法(連結):

第一步 — 在此 page component 宣告 'use client' ,引入有用到 Konva 和 react-konva 的元件時,採用 dynamic import 並設定關閉 SSR

[embed]

但要注意若直接在 page.tsx 上宣告 'use client',會導致底下子元件被視為 Client Component,失去 Server-Side Rendering 的好處。

第二步 — 在 next.config 中加上

[embed]

功能實踐

Konva 中座標、旋轉、縮放的基準點有兩種:

  1. 圓形類:中心,像是 Circle、Ellipse、Ring、Star
  2. 矩形類:左上角,像是 Stage、Layer、Rect、Image

旋轉(rotate)

需求是有兩個按鈕可以針對圖片中心點進行每次 ±90° 的旋轉。

下圖中紅圈是旋轉的基準點,半透明的矩形是原先的模樣,另一矩形是順時針旋轉90度後的樣子。

矩形類型旋轉示意圖

矩形類型旋轉示意圖

假設想要根據矩形的中心旋轉,不像 CSS 可以用 transform-origin: 50% 50% 調整基準點,雖然 Konva 有提供一個 offset 屬性,設定成 width / 2height / 2 原則上可以做到,但是改完後會發現矩形的位置也跟著改變了,這樣的話後續計算方式就要有所調整,所以不太建議直接修改 offset官方參考),那麼只剩一種方式。

是時候來點三角函數了🤓

[embed]

Zoom In(放大)和 Zoom Out(縮小)

需求是根據滑鼠位置做每次 ±25% 的 Zoom In 或 Zoom Out。

這個函式計算出新的偏移量,基於新的縮放比例。它將原始的偏移量乘以新的縮放比例與舊的縮放比例之比,從而確定新的偏移量。

簡單來說,它允許你根據一個參考點(point)來縮放圖形,並且在縮放過程中保持該參考點的位置不變。

[embed]

實際調用函式

先定義全域 State 和 Type、Enum:

[embed]

利用 Queue 的資料結構(First-In-First-Out)處理被 push 進來的 action:

[embed]

還有很多有趣又燒腦的實作在後頭,但礙於篇幅,就先到此為止,有機會再來寫續章

謝謝各位看官~

結語

一開始聽到這個類「小畫家」的需求時,沒有相關的領域知識,同事們也沒有實作過,真的讓我感到無所適從,曾想過是不是要一邊研究 Canvas API 一邊實作,但這麼一來時程就得拉很長,幸好活在這個大開源時代,已經有無數大神幫忙造好「輪子」了,剩下的只要好好梳理需求,拆分成小問題,一步步建構解決方案,Divide and conquer,離完成就不遠了吧!

參考資料


메타데이터
post_id
013fa0f679bf
slug
react-js-canvas-konva-js-打造專屬的網頁小畫家-013fa0f679bf
url
https://medium.com/twjoin/react-js-canvas-konva-js-%E6%89%93%E9%80%A0%E5%B0%88%E5%B1%AC%E7%9A%84%E7%B6%B2%E9%A0%81%E5%B0%8F%E7%95%AB%E5%AE%B6-013fa0f679bf
canonical_url
https://medium.com/twjoin/react-js-canvas-konva-js-%E6%89%93%E9%80%A0%E5%B0%88%E5%B1%AC%E7%9A%84%E7%B6%B2%E9%A0%81%E5%B0%8F%E7%95%AB%E5%AE%B6-013fa0f679bf
author_url
https://medium.com/@cj.yang0225
status
ok
fetched_at
2026-07-23 11:29:59