Day 3 - 用 canvas 复刻 小画家 画笔

说明

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


<<:  Day11-"一维阵列练习"

>>:  [第四天]从0开始的UnityAR手机游戏开发-介绍Unity介面和常用快捷键

[DAY 23] Visualize

前言 成长的过程中,有高峰有低潮,会有峰回路转的此起彼落,但也有柳暗花明的落泪感动。曾经我们也是那懵...

Day-7 带着童年的好朋友任天堂红白机、重新在 HDMI 电视上发光吧!

写了好几天的事前准备、我想大家应该都腻了。终於、准备到了一定程度、可以进入本文了。这篇文章主要的目的...

放弃实作 AES CBC 加密/解密

是的,如题 因为网路上找到的范例,几乎都是具备密码学知识基础才看得懂的 … 我完全无法使用 pyth...

Day29

在64位元系统指标是64/8 = 8bytes,而double也是8bytes若指标指向更小的型态如...

Day 4 基本型别 - part 1

今天要介绍 TypeScript 的基本型别,TypeScript 跟 JavaScript 一样拥...