Use React to build a web app: simple image editor (part 1)

Tobby Kuo
5 min readJan 31, 2019

--

這是我們吃貨台大團隊內部在發文之前會使用到的圖片編輯器,而這篇文章的內容大致上就是紀錄這個工具的建置,並著重在我遇到的問題以及後來的解法。

整個專案基本上是這樣子的:使用React作為前端框架,處理使用者輸入的資料、利用Javascript處理圖片檔案的讀取並用原生的canvas來繪製圖片、再加上Node.js內建的EventEmitter來控制進度條的運行、最後打包成靜態網站放到firebase上,完全靠前端運作。

(吃貨台大小介紹:大約成立於兩年半前,專注於報導台灣大學周邊飲食的媒體,目前累積了大約三萬名的粉絲,也曾加入台大車庫以及Appworks等新創加速器,目前轉為台大校內學生社團負責經營。)

/

/

/

/

/

/

/

本文因為篇幅有點長所以會分成兩篇,架構大概長這樣:

Part.1

  1. 團隊需求
  2. 釐清問題點

Part.2:點這

  1. React元件架構
  2. Canvas繪製
  3. 繪製進度條
  4. 架站以及Cache
  5. 未來功能

原始碼的部分我放在這:https://github.com/tobby168/image-editor-by-react,不過其中沒有放上firebase相關檔案以及其他靜態資源。

團隊需求

這張圖是一篇我們的普通報導,基本上是四張照片且第一張為封面圖片的形式(若發文在Instagram則只會顯示第一張),而這次的照片編輯器主要就是為了製作封面圖片,也就是加上浮水印以及標題。

早期在我們還沒有這個編輯器之前,我們要產生封面照大概有兩種方式:

把手機裡的照片傳到電腦、再用Photoshop。

或是使用手機版Photoshop加上logo浮水印後,另外使用可以自行安裝字型的app加上標題。

兩種方法都有些共同的缺點,不外乎就是動作繁複、太多操作導致照片畫質減損、多人作業時圖片不統一⋯⋯等。

也因為我們的團隊從一開始2個人到3、4個人又到現在社團20人左右在經營,這些問題隨著人數增加而越趨明顯,而開始有了自己建立工具的想法。

而團隊對於這個工具的需求,不外乎就是:手機即可操作、不減損畫質、使用統一字型、logo位置固定

/

釐清問題點

在開始開發之前,由於之前沒有做過太多處理圖片相關的專案,因此我先把幾個預想中會碰到的困難點列出來,並且在大量找尋資料的過程中找到一些解法:

  1. 手機即可操作:這個算是所有問題裡面最容易的,因為我不可能建置雙平台的app,所以基本上只有web app一途。本來也曾考慮是否可以用網路上現成的工具就好,但是考量到團隊需求以及未來的擴展性,決定還是自己開發。
  2. 是否需要後端:由於我們吃貨台大有提供給粉絲一個查詢餐廳的聊天機器人,所以有將報導過的店家都存入資料庫(封面照片、價位、地點、種類⋯⋯大概有這些欄位),因此在一開始有考慮要不要把繪製完的圖片直接傳到資料庫裡。不過後來發現臉書的圖片url會定期更改token,所以這件事還是交給本來上傳商家資料的工具。
  3. 不減損圖片畫質:這件事其實牽扯到兩個層面,一個是在繪製圖片之前要如何在符合不同手機解析度的前提下預覽成品,另一個則是繪製時要如何保持原圖片解析度。
    而關於這個問題,我想到的解法是:在讀取完圖片後,繪製在canvas內的預覽只有960px * 960px,並在之後定位元件、繪製時套用一樣的縮放比,來使預覽圖符合每隻手機的解析度、而成品又可以原解析度輸出。(詳情還是看後面的canvas繪製部分)
  4. 統一字型:由於我們標題使用的字型還蠻少見的,所以手機基本上不會內建,因此必須要用靜態資源的方式提供給使用者端,但是這也導致了後來遇到的兩個問題。
    第一個是字型在使用到的時候才開始載入,因此使用者剛開始預覽時,很可能會發生輸入了標題但是字型還沒載完的情況,本來想使用lazy load之類的方式處理這個問題,但後來又更簡單暴力的作法:用一個隱藏的元素在一開始就使用字型,讓使用者端一開始就進行載入。
    第二個問題是使用者端在請求字型檔時佔用的資源實在太大了,而且每次也才用到幾個字,如果每次重開網站都要重新載入的話恐怕傳輸量會超過firebase的免費額度,所以後來我使用cache的方式把字型存放在使用者端,這部分是要去更改firebase的設定。
  5. 使用者控制標題位置:因為想要讓使用者可以自由在預覽圖上面拖曳標題字放到自己想要的位置,所以我先在預覽圖上放上假的標題字元素,再紀錄使用者拖移距離後,繪製圖片時再將標題繪製在對的位置上。
    而為了實現拖移這個動作,本來是打算使用drag and drop這個html5提供的功能,後來發現這個功能比較適合把元素放入一些指定的元素裡面,沒辦法像我想像的這樣操作,於是最後還是直接去處理touch event。
  6. 手機的效能:由於手機的瀏覽器效能一定會比電腦的低一些,所以預想中是要留一些空間彌補效能差距。主要的差距應該在於字型的讀取速度以及canvas繪製圖片的效能。(倒是還好沒有遇到手機瀏覽器對canvas支援度不夠之類的問題)
    因為上面的理由,用隱藏元素先載入字型也是我為了防止讀取速度太慢的方法,而canvas效能我則是希望能夠有個進度條來讓使用者知道繪製是不是卡住了,而非讓使用者空等。
    在查詢進度條相關的資料時,我發現要做出一個實時顯示真正進度的進度條其實是蠻複雜的,所以我打算做一個假的!我把繪製的過程大致上分成四步驟:繪製原解析度圖片、上logo浮水印、上標題字、轉成base64檔案,而每完成這些步驟就等於是到了一個檢查點,就發送一個event emit,讓進度條跑到25%、50%、75%、100%的位置。

再來就要進入到一些解釋程式碼的部分,看第二篇

--

--

Tobby Kuo

Microsoft Software Engineer, focus on infrastructure developments for big data in distributed environments.