본문 바로가기
블로그 이미지

방문해 주셔서 감사합니다! 항상 행복하세요!

  
   - 문의사항은 메일 또는 댓글로 언제든 연락주세요.
   - "해줘","답 내놔" 같은 질문은 답변드리지 않습니다.
   - 메일주소 : lts06069@naver.com


Html 캔버스

리액트 환경에서의 캔버스 (React Html canvas)

야근없는 행복한 삶을 위해 ~
by 마샤와 곰 2022. 6. 3.

 

리엑트로 다양한 기능을 만들다가 캔버스(canvas)를 사용하기 위해 구글링을 좀 해 보았습니다.

대략 아래처럼 캔버스 객체(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축
}

 

그러면 이제 간단한 선 그리기 기능을 만들어 보겠습니다.

구글링을 하면서 조금 아쉬웠던 부분이 여러 글들이 캔버스의 컨텍스트 객체를 사용 할 때 save, restore 하는 부분의 코드를 생략해서 소개한다는 점 입니다.

또한 스타일 설정이 같은 경우라면 1번만 선언해도 될 부분을 지속해서 선언하는 코드도 있습니다.

 //save는 어디갔나요?
ctx.lineJoin = 'round';  //중복되는 요런곳
ctx.lineWidth = 3; //중복되는 요런곳
ctx.strokeStyle = 'red'  //중복되는 요런곳
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.closePath();
ctx.stroke();
//restore는 어디갔나요?

 

이처럼 중복된 명령이나 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;

 

실제 동작하는 모습 입니다.

잘되네요..ㅋ

 

아래 제 포스팅을 통해 캔버스 사용법 및 활용 방법에 대해서 확인하실 수 있습니다.

 

1. 캔버스 차트만들기

https://lts0606.tistory.com/category/Html 캔버스/Html 캔버스 튜토리얼 (내가만든 차트!)

 

'Html 캔버스/Html 캔버스 튜토리얼 (내가만든 차트!)' 카테고리의 글 목록

Hello world!

lts0606.tistory.com

 

2. 캔버스 에니메이션

https://lts0606.tistory.com/category/Html 캔버스/Html 캔버스 에니메이션

 

'Html 캔버스/Html 캔버스 에니메이션' 카테고리의 글 목록

Hello world!

lts0606.tistory.com

 

 

 

리엑트에서 HTML Canvas 사용방법은 순수 바닐라의 환경에서와 별반 차이가 없습니다.

이상으로 간단하게 살펴본 리액트 캔버스(React Html canvas)였습니다.

궁금한점 또는 틀린부분은 언제든 연락주세요!👻

반응형
* 위 에니메이션은 Html의 캔버스(canvas)기반으로 동작하는 기능 입니다. Html 캔버스 튜토리얼 도 한번 살펴보세요~ :)
* 직접 만든 Html 캔버스 애니메이션 도 한번 살펴보세요~ :)

댓글