canvas實現手動繪製矩形

南风晚来晚相识發表於2024-08-15

開場白

雖然在實際的開發中我們很少去繪製流程圖
就算需要,我們也會透過第3方外掛去實現
下面我們來簡單實現流程圖中很小的一部分
手動繪製矩形

繪製一個矩形的思路

我們這裡繪製矩形
會使用到canvas.strokeRect(x,y, w, h)方法繪製一個描邊矩形
x:矩形起點的 x 軸座標。
y:矩形起點的 y 軸座標。
width:矩形的寬度。正值在右邊,負值在左邊。
height:矩形的高度。正值下降,負值上升。
注意一下w,h這兩個引數的正直和負值。
如果w,h是負數,會出現了2個斜著對稱的矩形

繪製一個靜態矩形

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    html,body{
      /* 去除瀏覽器內建的margin */
      margin: 0;
      /* 整個頁面鋪滿 */
      height: 100%;
      width: 100%;
    }
  </style>
</head>
<body>
  <canvas id="canvas"></canvas>
</body>
<script>
  // 獲取canvas元素
  let canvasEle = document.getElementById('canvas')
  // 獲取canvas的上下文
  const ctx = canvasEle.getContext('2d')
  // 設定canvas的大小(寬高) 與螢幕一樣寬高
  const screenSize = document.documentElement
  canvasEle.width =  window.screen.availWidth
  canvasEle.height =  window.screen.availHeight
  // 給矩形的設定顏色,設定顏色一定要在繪製之前,否則將會不生效
  ctx.strokeStyle = '#a0a'
  // 繪製一個路徑模式是的矩形,起始座標100,100, 寬300,高280
  ctx.strokeRect(100,100,300,280)
</script>
</html>

手動繪製矩形的基本思路

透過上面我們實現了靜態繪製矩形。
如果我們想要手動畫一個矩形,需要實現以下幾個步驟
1.給canvas註冊滑鼠按下事件,在按下的時候記錄矩形的起始座標(x,y)
與此同時,還需要在按下時註冊滑鼠移動事件和抬起事件。
2.在滑鼠移動的時候,透過計算得到矩形的寬和高。
計算矩形的寬度和高度時,我們要使用絕對值進行計算。
計算後立即繪製矩形
3.在滑鼠抬起時,移除之前註冊的滑鼠移動事件和抬起事件

手動繪製矩形

<script>
// 獲取canvas元素 oCan
let canvasEle = document.getElementById('canvas')
// 獲取canvas的上下文
const ctx = canvasEle.getContext('2d')
// 設定canvas的大小(寬高) 與螢幕一樣寬高
const screenSize = document.documentElement
canvasEle.width =  window.screen.availWidth
canvasEle.height =  window.screen.availHeight
// 所有的矩形資訊
let rectArr = []
// 給canvas註冊事件按下事件
canvasEle.addEventListener('mousedown',canvasDownHandler)
function canvasDownHandler(e){
  console.log('按下', e)
  rectArr = [e.clientX,e.clientY ]
  // 按下的時候需要註冊移動事件
  canvasEle.addEventListener('mousemove', canvasMoveHandler)
  // 抬起事件
  canvasEle.addEventListener('mouseup', canvasMouseUpHandler)
}
// 移動的時候我們需要記錄起始點和結束點,然後就可以繪製矩形了
function canvasMoveHandler(e){
  console.log('我們在移動了', e)
  // 在移動的時候就開始繪製矩形
  drawRect(rectArr[0], rectArr[1], e.clientX, e.clientY)
}

function drawRect(x1,y1,x2,y2){
  // 在計算矩形的寬高時,我們需要使用絕對值來進行計算
  // 此時此刻移動的座標減去最初按下的座標就是矩形的寬和高
  // 如果不用絕對值,可能會出現2個矩形
  let rectWidth = Math.abs(x2-x1)
  let rectHeight = Math.abs(y2-y1)
  // 儲存矩形的座標資訊
  rectArr = [x1,y1,rectWidth,rectHeight]
  ctx.strokeRect(...rectArr)
}
// 當我們滑鼠抬起的時候要移除之前註冊移動事件和抬起事件
function canvasMouseUpHandler(){
  canvasEle.removeEventListener('mousemove', canvasMoveHandler)
  canvasEle.removeEventListener('mouseup', canvasMouseUpHandler)
}
</script>


發現問題出現多餘的路徑

透過上面這張圖片,我們雖然繪製手動繪製出了矩形。
但是出現了重複的路徑。
我們先分析一下出現重複路徑的原因。
我們在每次移動的過程中,都會繪製矩形。
只要我們在繪製前,清空矩形是不是就可以解決這個問題
我們來嘗試一下

在繪之前清除多餘的路徑

function drawRect(x1,y1,x2,y2){
  // 在計算矩形的寬高時,我們需要使用絕對值來進行計算
  // 此時此刻移動的座標減去最初按下的座標就是矩形的寬和高
  let rectWidth = Math.abs(x2-x1)
  let rectHeight = Math.abs(y2-y1)
  // 儲存矩形的座標資訊
  rectArr = [x1,y1,rectWidth,rectHeight]
  // 在繪製矩形前,我們將矩形清空,然後在繪製,就不會出現多餘的路徑了
  ctx.clearRect(0,0,  canvasEle.width, canvasEle.height)
  // 重新繪製矩形
  ctx.strokeRect(...rectArr)
}

機智的小夥伴發現問題了?

有些機智的小夥伴發現:
如果我起始點是右下角,終點是左上角。
即:使用者從(900, 1000)拖動到(50, 50)這種情況
按照這樣的方向繪製矩形,是不是會出現問題呢?
確實會出現問題。
此時繪製的矩形不會隨著我們的方向進行繪製。請看下面的圖
如何處理這個問題呢?

主角閃亮登場 canvas.rect

canvas.rect(x,y,w,h)該方法建立一個矩形路徑
x:矩形起點的 x 軸座標。
y:矩形起點的 y 軸座標。
width:矩形的寬度。正值在右邊,負值在左邊。
height:矩形的高度。正值下降,負值上升。
這個方法是建立一個矩形路徑,建立的路徑並不會直接繪製在畫布上。
需要呼叫stroke()或fill()才能顯示在畫布上。

使用canvas.rect 繪製矩形路徑

function drawRect(x1,y1,x2,y2){
  let rectWidth = Math.abs(x2-x1)
  let rectHeight = Math.abs(y2-y1)
  // 繪製之前先清空之前實時移動產生的多餘的矩形路徑
  ctx.clearRect(0,0,  canvasEle.width, canvasEle.height)
  // 開始畫線
  ctx.beginPath();  
  // 繪製路徑矩形
  ctx.rect(Math.min(x1, x2), Math.min(y1, y2), rectWidth, rectHeight);  
  // 繪製形狀的輪廓。
  ctx.stroke(); 
}

canvas.strokeRect與canvas.rect的異同

區別1功能性: canvas.strokeRect:繪製的是邊框。canvas.rect建立矩形的路徑(不繪立即繪製在矩形上)
區別2即時性: canvas.strokeRect是立即繪製。canvas.rect不是立即繪製,需要呼叫stroke()或fill()才能繪製
相同點:
1.都是繪製矩形
2.接受的引數相同
3.都是透過strokeStyle(顏色)和lineWidth(線的粗細)等來設定樣式

連續繪製多個矩形

function drawRect(x1,y1,x2,y2){
  let rectWidth = Math.abs(x2-x1)
  let rectHeight = Math.abs(y2-y1)
  let endX = Math.min(x1, x2)
  let endY = Math.min(y1, y2)
  // 繪製之前先清空之前實時移動產生的多餘的矩形路徑
  ctx.clearRect(0,0,  canvasEle.width, canvasEle.height)
  // 繪製之前那些儲存在 beforeRectArr 陣列中的矩形
  allRectInfoArr = [endX, endY, rectWidth, rectHeight]
  ctx.clearRect(0,0,  canvasEle.width, canvasEle.height)
  beforeRectArr.forEach(element => {
    ctx.beginPath(); 
    ctx.strokeRect(...element)
    ctx.stroke();
  });
  
  // 開始本次路徑
  ctx.beginPath(); 
  // 繪製本次的矩形路徑
  ctx.rect(...allRectInfoArr); 
  // 開始填充矩形
  ctx.stroke(); 
}
// 當我們滑鼠抬起的時候要移除之前註冊移動事件和抬起事件
function canvasMouseUpHandler(){
  savaBeforeRect()
  canvasEle.removeEventListener('mousemove', canvasMoveHandler)
  canvasEle.removeEventListener('mouseup', canvasMouseUpHandler)
}
function savaBeforeRect(){
  beforeRectArr.push(allRectInfoArr)
}

全部程式碼

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    html,body{
      /* 去除瀏覽器內建的margin */
      margin: 0;
      /* 整個頁面鋪滿 */
      height: 100%;
      width: 100%;
    }
  </style>
</head>
<body>
  <canvas id="canvas"></canvas>
</body>
<script>
  // 獲取canvas元素 oCan
  let canvasEle = document.getElementById('canvas')
  // 獲取canvas的上下文
  const ctx = canvasEle.getContext('2d')
  // 設定canvas的大小(寬高) 與螢幕一樣寬高
  const screenSize = document.documentElement
  canvasEle.width =  window.screen.availWidth
  canvasEle.height =  window.screen.availHeight
  // 給矩形的設定顏色
  // ctx.strokeStyle = '#a0a'
  // // 繪製一個路徑模式是的矩形,起始座標100,100, 寬300,高280
  // ctx.strokeRect(100,100,400,280)
  // 矩形資訊
  let rectArr = []
  // 所有的矩形資訊
  allRectInfoArr = []
  let beforeRectArr =[]
  // 給canvas註冊事件按下事件
  canvasEle.addEventListener('mousedown',canvasDownHandler)
  function canvasDownHandler(e){
    rectArr = [e.clientX,e.clientY ]
    // 按下的時候需要註冊移動事件
    canvasEle.addEventListener('mousemove', canvasMoveHandler)
    // 抬起事件
    canvasEle.addEventListener('mouseup', canvasMouseUpHandler)
  }
  // 移動的時候我們需要記錄起始點和結束點,然後就可以繪製矩形了
  function canvasMoveHandler(e){
    // 在移動的時候就開始繪製矩形
    drawRect(rectArr[0], rectArr[1], e.clientX, e.clientY)
  }

  function drawRect(x1,y1,x2,y2){
    let rectWidth = Math.abs(x2-x1)
    let rectHeight = Math.abs(y2-y1)
    let endX = Math.min(x1, x2)
    let endY = Math.min(y1, y2)
    // 繪製之前先清空之前實時移動產生的多餘的矩形路徑
    ctx.clearRect(0,0,  canvasEle.width, canvasEle.height)
    // 繪製之前那些儲存在 beforeRectArr 陣列中的矩形
    allRectInfoArr = [endX, endY, rectWidth, rectHeight]
    beforeRectArr.forEach(element => {
      ctx.beginPath(); 
      ctx.strokeRect(...element)
      ctx.stroke();
    });
    
    // 開始本次路徑
    ctx.beginPath(); 
    // 繪製本次的矩形路徑
    ctx.rect(...allRectInfoArr); 
    // 開始填充矩形
    ctx.stroke(); 
  }
  // 當我們滑鼠抬起的時候要移除之前註冊移動事件和抬起事件
  function canvasMouseUpHandler(){
    savaBeforeRect()
    canvasEle.removeEventListener('mousemove', canvasMoveHandler)
    canvasEle.removeEventListener('mouseup', canvasMouseUpHandler)
  }

  function savaBeforeRect(){
    beforeRectArr.push(allRectInfoArr)
  }
</script>
</html>

尾聲

如果小夥伴覺得我寫的不錯的話
可以給我點個贊嗎?感謝了。
後面會繼續寫:
如何選中矩形,更改矩形大小。
如何在矩形上新增文字。
如何繪製圓,箭頭符號等

相關文章