微信小程式 canvas圓角矩形的繪製

清夜發表於2018-08-22

微信小程式允許對普通元素通過 border-radius的設定來進行圓角的繪製,但有時候在使用 canvas繪圖的時候,也需要圓角,例如需要將頁面上某塊區域匯出為圖片下載到本地的時候,常用的解決方法就是使用 canvas將這塊區域繪製出來,最後匯出 canvas即可,但是 canvas沒有直接提供圓角的繪製 api,所以需要 曲線救國


圓角矩形與一般矩形的區別在於,前者的四個角都是圓弧,所以只需要將一般矩形的四個角切掉,換成圓弧即可,如下圖就是一個一般矩形被切掉了四個角的樣子:

微信小程式 canvas圓角矩形的繪製

很明顯,切掉了四個角的矩形,剩下其實就是四條 line,既然如此,完全可以跳過繪製矩形然後切角這一步,因為切角的結果就是四條邊(line),直接繪製四條邊即可。 然後在每兩條邊的缺角處繪製弧度為 0.5 * Math.PI 的圓弧,最後這四條邊與四個圓弧所封閉的圖形就是圓角矩形:

微信小程式 canvas圓角矩形的繪製

原理知道了,程式碼就很好寫了,這裡只說幾個注意點:

  • 封閉圖形的 fillStyle顏色設定為 transparent

想將封閉路徑的圖形繪製下來,需要呼叫 strokefill方法,預設 strokefill的顏色是 black,但是這裡有個問題, 圓弧的繪製可能會出現鋸齒或者糊邊,如果 strokefill的顏色,與你所需要繪製的圓角矩形的邊緣色調不一致,這種糊邊的感覺會比二者色調一致的更明顯, 下圖第一個為色調一致,第二個為色調不一致的情況:

微信小程式 canvas圓角矩形的繪製
微信小程式 canvas圓角矩形的繪製

不過據我觀測,只要不是特意放大仔細看,無論是色調是否一致,其實一般人很難注意到糊邊的事情

  • clip

繪製好了圓角選區之後,還需要呼叫 ctx.clip方法來裁剪選區

  • saverestore

如果這個矩形選區只是 canvas畫布的一部分,為了避免對後續的影響,最好在 beginPath之前,將之前的動作 save,然後畫完後再 restore

一個關於 canvas上繪製圓角圖片,並下載到本地 的可執行示例程式碼已經放到 github上了,註釋也比較詳細,需要的可自取

其中關鍵程式碼如下:

/**
  * 
  * @param {CanvasContext} ctx canvas上下文
  * @param {number} x 圓角矩形選區的左上角 x座標
  * @param {number} y 圓角矩形選區的左上角 y座標
  * @param {number} w 圓角矩形選區的寬度
  * @param {number} h 圓角矩形選區的高度
  * @param {number} r 圓角的半徑
  */
function roundRect(ctx, x, y, w, h, r) {
  // 開始繪製
  ctx.beginPath()
  // 因為邊緣描邊存在鋸齒,最好指定使用 transparent 填充
  // 這裡是使用 fill 還是 stroke都可以,二選一即可
  ctx.setFillStyle('transparent')
  // ctx.setStrokeStyle('transparent')
  // 左上角
  ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5)

  // border-top
  ctx.moveTo(x + r, y)
  ctx.lineTo(x + w - r, y)
  ctx.lineTo(x + w, y + r)
  // 右上角
  ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2)

  // border-right
  ctx.lineTo(x + w, y + h - r)
  ctx.lineTo(x + w - r, y + h)
  // 右下角
  ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5)

  // border-bottom
  ctx.lineTo(x + r, y + h)
  ctx.lineTo(x, y + h - r)
  // 左下角
  ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI)

  // border-left
  ctx.lineTo(x, y + r)
  ctx.lineTo(x + r, y)

  // 這裡是使用 fill 還是 stroke都可以,二選一即可,但是需要與上面對應
  ctx.fill()
  // ctx.stroke()
  ctx.closePath()
  // 剪下
  ctx.clip()
}
複製程式碼

相關文章