iT邦幫忙

2021 iThome 鐵人賽

DAY 3
0
Modern Web

Canvas 小錦囊系列 第 3

Day 3 - 用 canvas 復刻 小畫家 畫筆

  • 分享至 

  • xImage
  •  

說明

根據 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


上一篇
Day 2 - 用 canvas 復刻 小畫家 材料準備
下一篇
Day 4 - 用 canvas 復刻 小畫家 填入色彩, 橡皮擦
系列文
Canvas 小錦囊30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言