[React] Html canvas 알아보기

H_Chang·2023년 12월 22일
1

캔버스 객체(context)에 대해서 상태(state)를 유지하는 코드가 많다.

const ref = useRef(null);
const [ctx, setCtx] = useState();  //캔버스 컨텍스트를 useState로 상태관리

useEffect(()=>{
    const canvas = ref.current
    const context = canvas.getContext('2d')
    setCtx(context)
}, [])


<canvas ref={canvasRef} > </canvas>
  • useEffect를 통해서 캔버스의 컨텍스트 객체(context)를 가져오기 위해서 ctx값을 state로 관리해야 한다.
  • 그렇지 않은경우 ctx객체에 대한 상태관리가 필요하다고 "경고" 문구를 계속해서 나타난다.

캔버스에 직접 그림을 그리는 기능을 위해서는 엘리먼트에 이벤트를 붙여주면 쉽게 가능하다.
-자바스크립트의 돔(dom)에 접근하는 네이티브코드를 쓰지 않고 아래처럼 간결하게 쓰는 것이 낫지않나 라고 생각한다.

const canvasRef = useRef(null);

useEffect(() => {
    const canvas = canvasRef.current;
    setCtx(canvas.getContext('2d'));
}, [])

const canvasEventListener = (event, type) => { 
    console.log(event, type)
}

return (  //마우스 이벤트 정의
    <canvas ref={canvasRef}
        onMouseDown={(event) => { canvasEventListener(event, 'down') }}
        onMouseMove={(event) => { canvasEventListener(event, 'move') }}
        onMouseLeave={(event) => { canvasEventListener(event, 'leave') }}
        onMouseUp={(event) => { canvasEventListener(event, 'up') }}
    >
    </canvas>
)
  • event로 넘어온 객체에는 마우스 event값이 존재하고, 또한 지정된 target에 대한 값이 존재 한다.
  • 그러면 캔버스 위의 좌표를 구하는 공식은 event와 event.target에서의 값을 빼 주는 것으로 만들 수 있다.
const canvasEventListener = (event, type) => {
    let x = event.clientX - event.target.offsetLeft;  //x축
    let y = event.clientY - event.target.offsetTop;  //y축
}

간단한 선 그리기 기능

ctx.lineJoin = 'round';  //중복되는 요런곳
ctx.lineWidth = 3; //중복되는 요런곳
ctx.strokeStyle = 'red'  //중복되는 요런곳
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.closePath();
ctx.stroke();
  • 이처럼 중복된 명령이나 save와 restore를 하지 않아서 서로의 스타일이 믹스되는 현상을 방지하기위해서는 좀 더 조심해야될 필요가 있다.

아래는 이러한 부분을 고려하여 작성된 코드 이다.

  const canvasRef = useRef(null);

  const array = []
  useEffect(() => {
    const canvas = canvasRef.current;
    const context = canvas.getContext('2d');
    context.lineJoin = 'round';  //중복되는 속성은 1번으로 끝내게 합니다.
    context.lineWidth = 3;  //중복되는 속성은 1번으로 끝내게 합니다.
    context.strokeStyle = 'blue' //중복되는 속성은 1번으로 끝내게 합니다.
    setCtx(context);
  }, [])

  const canvasEventListener = (event, type) => {
    let x = event.clientX - event.target.offsetLeft;
    let y = event.clientY - event.target.offsetTop;
    if (type === 'move') {
      if (array.length === 0) {
        array.push({ x, y })
      } else {
        ctx.save()  //다른 스타일 또는 속성을 줄 수 있으므로 항상 save를 합니다.
        ctx.beginPath();
        ctx.moveTo(array[array.length - 1].x, array[array.length - 1].y);
        ctx.lineTo(x, y);
        ctx.closePath();
        ctx.stroke();
        ctx.restore()  //작업이 끝나면 기존 스타일 또는 속성으로 되돌려 줍니다.
        array.push({ x, y })
      }

    } else if (type === 'leave') {
      ctx.save()
      while (array.length) array.pop();
      ctx.clearRect(0, 0, 2580, 2580)
      ctx.restore()
    
  • array라는 배열은 마우스가 움직 일 때 좌표값을 넣기위한 값 이다.

  • moveTo 함수는 캔버스의 위치로 좌표값을 옮기는 기능이며, lineTo 함수는 moveTo 한 곳에서부터 선을 그리는 기능 이다.

  • type이라는 변수에 각각의 마우스 이벤트 종류가 존재하여 "움직일때" 와 "벗어날때" 를 구분지어서 효과를 부여 해 보았다.

아래는 최종 완성된 코드 이다.

import React, { useRef, useEffect, useState } from 'react'

const defaultStyle = { border: '1px solid gray', display: 'inline-block', margin: '1rem' }

function Main(arg) {

  const [ctx, setCtx] = useState();

  const canvasRef = useRef(null);

  const array = []
  useEffect(() => {
    const canvas = canvasRef.current;
    const context = canvas.getContext('2d');
    context.lineJoin = 'round';
    context.lineWidth = 3;
    context.strokeStyle = 'blue'
    setCtx(context);
  }, [])

  const canvasEventListener = (event, type) => {
    let x = event.clientX - event.target.offsetLeft;
    let y = event.clientY - event.target.offsetTop;
    if (type === 'move') {
      if (array.length === 0) {
        array.push({ x, y })
      } else {
        ctx.save()
        ctx.beginPath();
        ctx.moveTo(array[array.length - 1].x, array[array.length - 1].y);
        ctx.lineTo(x, y);
        ctx.closePath();
        ctx.stroke();
        ctx.restore()
        array.push({ x, y })
      }

    } else if (type === 'leave') {
      ctx.save()
      ctx.clearRect(0, 0, 2580, 2580)
      ctx.restore()
    }
  }

  return (
    <div className='container' >
      <canvas ref={canvasRef} style={defaultStyle}
        onMouseDown={(event) => { canvasEventListener(event, 'down') }}
        onMouseMove={(event) => { canvasEventListener(event, 'move') }}
        onMouseLeave={(event) => { canvasEventListener(event, 'leave') }}
        onMouseUp={(event) => { canvasEventListener(event, 'up') }}
      >

      </canvas>

    </div>
  );
}

export default Main;
profile
프론트 엔드 시작하는 뉴비!

0개의 댓글