根據 MDN 的教學
一開始canvas為空白,程式碼腳本需要先存取渲染環境,在上面繪圖,然後才會顯現影像。 素有一個方法(method)叫getContext(),透過此方法可以取得渲染環境及其繪圖函數(function);getContext()輸入參數只有渲染環境類型一項,像本教學所討論的2D繪圖,就是輸入”2d”。
所以當我們要在畫布上操作時,要將畫布改寫成方法,使用 getContext(),像是這樣
 const ctx = canvasRef.current.getContext("2d");
再來按照畫筆的操作,行為如下:
開始畫畫(onMouseDown)-> 滑鼠按著移動繪製 (mousemove)-> 繪製完成(onMouseUp)
所以當滑鼠下壓時,我們應該給予一個 isDrawing = true;,告訴下一個步驟,當前是在繪製狀態,同理,在繪製完成時 isDrawing = false;
這是本段的重點,當每個點在移動時進行繪製。
 /**
 * 移動時處理每一個點
 * @param {Object} e 移動事件
 */
const handleMouseMove = (e: MouseEvent) => {
  if (isDrawing) {
    const point = { x: e.offsetX, y: e.offsetY };
    handleDrawCanvas(point);
  }
};
繪製
const handleDrawCanvas = (point: { x: number; y: number }) => {
  const ctx = canvasRef.current.getContext("2d");
  if (tool === "pencil") {
    ctx.strokeStyle = activeColor;
    ctx.lineWidth = 1;
    // ctx.globalAlpha = opacity;
    ctx.lineCap = "round"; // 頭尾圓弧的線條
    ctx.lineJoin = "round"; // 交會時圓角
    ctx.beginPath();
    ctx.moveTo(lastPoint?.x, lastPoint?.y); // 下筆位置
    ctx.lineTo(point?.x, point?.y);
    ctx.stroke();
    lastPoint = { x: point?.x, y: point?.y }; // 記錄最後停止位置
    ctx.closePath();
  }
 
};
每當一個點到下一個點時要記錄最後停止的位置,然後就可以點接著點連成線~
畫筆參數
在這邊學習幾個參數,可以依照喜好給予內容
 ctx.strokeStyle = "#000"; // 畫筆顏色
 ctx.lineWidth = 1; // 畫筆寬度
 ctx.lineCap = "round"; // 頭尾圓弧的線條
 ctx.lineJoin = "round"; // 交會時圓角
補上 MDN link
顏色:strokeStyle
畫筆粗細:lineWidth
頭尾處:lineCap
交界處:lineJoin
前面的準備就緒後,就可以開始用畫筆畫畫啦!來看看目前完成的程式碼
CanvasBox/index.tsx/**
 * 畫布區塊
 */
import { useEffect, useState, useRef, useCallback } from "react";
import { Wrapper, MainCanvas } from "./style";
import { useRecoilValue } from "recoil";
import { activeColorState, toolState } from "../../data/atom";
let lastPoint: { x?: number; y?: number } | null = {}; // 滑鼠移動的上一個點
const CanvasBox = () => {
  const activeColor = useRecoilValue(activeColorState);
  const tool = useRecoilValue(toolState);
  const canvasRef = useRef<any>(null);
  const [isDrawing, setIsDrawing] = useState<boolean>(false);
  useEffect(() => {
    const handleDrawCanvas = (point: { x: number; y: number }) => {
      const ctx = canvasRef.current.getContext("2d");
      if (tool === "pencil") {
        ctx.strokeStyle = activeColor;
        ctx.lineWidth = 1;
        // ctx.globalAlpha = opacity;
        ctx.lineCap = "round"; // 頭尾圓弧的線條
        ctx.lineJoin = "round"; // 交會時圓角
        ctx.beginPath();
        ctx.moveTo(lastPoint?.x, lastPoint?.y); // 下筆位置
        ctx.lineTo(point?.x, point?.y);
        ctx.stroke();
        lastPoint = { x: point?.x, y: point?.y };
      }
      ctx.closePath();
    };
    /**
     * 移動時處理每一個點
     * @param {Object} e 移動事件
     */
    const handleMouseMove = (e: MouseEvent) => {
      if (isDrawing) {
        const point = { x: e.offsetX, y: e.offsetY };
        handleDrawCanvas(point);
      }
    };
    if (canvasRef && canvasRef.current) {
      canvasRef.current.addEventListener("mousemove", handleMouseMove);
    }
    return () => {
      canvasRef.current.removeEventListener("mousemove", handleMouseMove);
    };
  }, [isDrawing, canvasRef, activeColor, tool]);
  /**
   * 滑鼠點下畫布後開始畫畫 or 填滿
   */
  const handleMouseDown = () => {
    setIsDrawing(true);
  };
 
 /**
   * 提起畫筆
   */
  const handleMouseUp = () => {
    if (isDrawing) {
      setIsDrawing(false);
      lastPoint = null;
    }
  };
  return (
    <Wrapper>
      <MainCanvas
        ref={canvasRef}
        height={500}
        width={500}
        onMouseDown={handleMouseDown}
        onMouseUp={handleMouseUp}
        onMouseOver={handleMouseUp}
      ></MainCanvas>
    </Wrapper>
  );
};
export default CanvasBox;
https://i.imgur.com/Iu3HlCQ.gif