前言

最近在搗騰一個 襪子圖案在線定製 的小程序,核心的需求大概是選擇一隻純色的襪子,然後客戶可以在襪子上面添加圖案, 最終生成一個設計圖保存後伺服器。定製的圖案可以旋轉,縮放和拖動,當然,還可以刪除。

內容比較初級, 因為我也只是一個 canvas 新手,本文僅是一次實踐的記錄.

實現-繪圖

其實剛開始接手這個項目的時候,我是打算用小程序的 web-view 組件來嵌套 h5 的, 因為小程序的 canvas 的坑,或者說小程序的坑,我是深有體會的,不過回過頭來想, web-view 我還沒再項目中用過, 還說不定會遇到什麼更大的坑呢,所以最終還是選擇小程序的 canvas

首先, 計算圖片的大小和位置,不能讓圖片超過畫布(我這裡是不能超過畫布的 80%),位置在畫布的正中間.

然後, 需要將原點移動到畫布的中心點 這點比較重要,等下下面的旋轉如果沒有這一步的話,會沿著圖片的左上角旋轉.

第三步,將目標圖像畫到畫板上,找個是沒啥難的,直接調用 ctx.drawImage 既可

if (!this.imgUrl) return
ctx.drawImage(this.imgUrl, x, y, this.tempImgWidth, this.tempImgHeight)

drawImage 之前需要 判斷一下目標圖像的路徑是否可以拿到圖片,如果圖片路徑有問題,會直接報錯,並且影響接下來的代碼執行, 相當於整個應用崩潰了.

我這裡因為都是本地圖片,所以只要判斷圖片存不存在即可,如果是網路圖片,還要先保存成本地圖片再 drawImage, 不然有可能會出現很多畫不出來的可能性.

由於中心點已經偏移了,這個時候, drawImage 的 x 和 y 需要向左上角偏移回去(imgWidth / -2, imgHeight / -2), 保持圖片的中心點跟canvas的原點重疊.

到此, 圖片就畫好了,如果有旋轉,要在這個時候旋轉.旋轉完以後,再畫外框和操作按鈕, 這樣可以保持外框和操作按鈕不隨著圖片旋轉(經過驗證,這種方式比跟著旋轉的好很多), 旋轉也很簡單,直接調用 ctx.rotate 即可, 旋轉的角度計算方法百度上有.我是直接拷貝的

然後就是操作按鈕和虛線框,參數跟 drawImage 一樣,因為是畫一個跟圖片一樣大小的框框嘛..

虛線框

ctx.setStrokeStyle(#fd749c)
ctx.setLineDash([5, 5], 10);
ctx.strokeRect(x, y, this.tempImgWidth, this.tempImgHeight)

操作按鈕,這裡的 r 是按鈕的半徑, d 是按鈕的直徑,無非就是在圖片的四個角,畫按鈕.

// 畫 刪除 按鈕
ctx.drawImage(/static/design/icons/delete.png, x - r, y - r, d, d)
// 畫 旋轉 按鈕
ctx.drawImage(/static/design/icons/rotate.png, x + this.tempImgWidth - r, y - r, d, d)
// 畫 縮放 按鈕
ctx.drawImage(/static/design/icons/scale.png, x + this.tempImgWidth - r, y + this.tempImgHeight - r, d, d)

最後再調用一下 ctx.draw() 完成繪圖. 所用到的 API 都很簡單,不過過程要計算的東西還是很多

實現-操作

繪圖已經完成了,那麼如何做到點擊或拖動操作按鈕,做響應的操作呢? 這就需要監聽 canvas 的 bindtouchstart, bindtouchmove, bindtouchend 三個事件, 然後 e.touches[0].xe.touches[0].y 可以獲取當前手指的位置, 在 bindtouchstart 的回調裡面判斷當前位置,如果跟某個操作按鈕重疊,就說明是操做,如果都沒有重疊,則是拖動,然後把操作記錄下來, 在 bindtouchmove 的回調裡面,獲取手指移動的距離和角度,然後根據 bindtouchstart 里記錄的操作, 調整對應的參數(拖動跟縮放,只要計算當前點的 x,y 跟 bindtouchstart 點的 x,y 的偏移量即可,旋轉的上面有提過,百度有), 然後重新調用 ctx.draw() 重新繪圖.

一個操作做完,可能需要複位一些東西,這個可以在 bindtouchend 的回調裡面去做

tips: 這裡的 e.touches[0].x 的 e 是回調函數帶的參數

保存定製圖

這些都畫好以後,需要把定製圖保存起來,指定圖保存的不單單是原來那個 canvas里的圖,還要連底圖(也就是那張襪子圖)一起保存起來, 需要一個襪子圖那麼大的 canvas(這個稱為臨時 canvas 吧),先把襪子畫進去,然後再把圖案畫上去,畫圖案上去的時候,由於畫布大小位置已經變了, 所以需要稍微計算一下,大概就是 x,y 要分別加上原來的 canvan 跟襪子圖左上角的距離.然後還要把外框跟操作按鈕去掉. 最後調用 ctx.draw 把圖畫出來..這裡有點需要注意的是,draw 其實是個非同步函數,如果你在ctx.draw() 下一行做保存操作, 那麼你保存的會是一個空白的圖片,draw 函數的第二個參數,是一個回調函數,想要獲取畫完圖的 canvas,要在這個回調函數裡面才能拿的到

ctx.draw(false, function () {
// 要先保存到臨時路徑
wx.canvasToTempFilePath({
canvasId: tempCanvas,
success: function (res) {
// 然後再保存到相冊
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success () {
wx.showToast({
title: 保存成功!,
icon: none,
duration: 2000
})
}
})
}
})
}
)

你感受到了微信小程序團隊滿滿的惡意了嗎? 這大概是我第一次寫這麼深的嵌套吧..傳說中的回調地獄.雖然可以用 promise 封裝解決, 不過為了幾個幾乎不會復用的 API 去封裝似乎不太划算. 不過總算還是保存成功了.

tips: 為了保存的時候這個臨時 canvas 不影響正常界面,這我的做法是對 臨時 canvas 設置 position:fixed; left:100vw;這兩個樣式,把它隱藏起來

最後

本文用的代碼都是基於 mpvue 框架的, 這個也是個比較基礎的東西,就不整個項目開源浪費 github 的空間了, 我把跟 canvas 有關代碼上傳到這裡, 僅供參考,單獨運行是肯定運行不起來, 如果有需要源碼,可以私下發郵件找我要.


推薦閱讀:
相关文章