[React] canvas로 트리 만들기

hhnn0·2022년 12월 22일
0
post-thumbnail

🎄 개요

Canvas와 mouse event를 이용하여 원하는 위치에 object를 그릴 수 있습니다.

GitHub
https://github.com/hhnn0/drawing-tree

🎄 시작에 앞서

기본 동작

  • 클릭을 통해 object를 선택할 수 있습니다.
  • 원하는 위치에 클릭을 한번 더 할 경우, 해당 위치에 object가 그려집니다.

이벤트

  • onClick
    해당 컴포넌트에서 마우스 클릭이 발생할 때 작동

  • onMouseMove
    해당 컴포넌트에서 마우스 이동이 발생할 때 작동

컴포넌트

  • Canvas
    트리 및 그림이 그려질 컴포넌트

  • SideBar
    object 리스트를 제공할 컴포넌트

State

  • selectedObj
const [selectedObj, setSelectedObj] = useState<string>('');

선택된 object

  • mousePosition
  const [mousePosition, setMousePosition] = useState<mousePosition>({
    positionX: null,
    positionY: null,
  });

interface mousePosition {
  positionX: number|null;
  positionY: number|null;
}

현재 마우스의 위치

Function

  • renderObjList
    object list 컴포넌트 생성

  • handleSelectedObj
    selectedObj 할당

  • handleMousePositionInSideBar
    사이드바에서 mousePosition 할당

  • drawObject
    캔버스에 object를 그려줌.


🎄 SideBar 구현

objList state

  const [objList, setObjList] = useState<string[]>(
    [treeObj1, treeObj2, treeObj3, treeObj4, treeObj5, treeObj6, treeObj7]
  );

objList의 초기값을 모두 할당해주었습니다.
추후에 object 추가 기능을 염두하여 state로 선언해주었습니다.

renderObjList

const renderObjList = () => {
  return objList.map((el, idx) => {
    return (
      <ObjContainer
        backgroundImg={el}
        onClick={(e) => {
          handleSelectedObj(el);
          handleMousePositionInSideBar({
            positionX: e.clientX,
            positionY: e.clientY,
          });
        }}
      />
     );
  });
};
  
const ObjContainer = styled.div<ObjContainerProps>`
cursor: pointer;
width: 100px;
height: 100px;
background-image: url(${(props)=>props.backgroundImg});
background-size:cover;
overflow:visible;
`

objList 배열의 원소들로 컴포넌트들을 생성해줍니다.
각 컴포넌트에 대해 onClick을통해 selectedObj와 mousePosition을 할당해주는 이벤트를 추가해주었습니다.


🎄 트리 만들기

<CanvasContainer  backgroundImg={Tree}>
  <canvas
    ref={canvasRef}
    width={window.innerWidth}
    height={window.innerHeight}
  />
</CanvasContainer>

const CanvasContainer = styled.div<ObjContainerProps>`
  position:relative;
  width:calc(100% - 120px);
  background-image:url(${(props)=>props.backgroundImg});
  background-position-x: center;
  background-position-y: -130px;
  background-size:853px 1280px;
  background-repeat: no-repeat;
  background-color: #8aacbf87;
`

canvas의 width와 height는 window 전체로 해주었습니다.
CanvasContainer의 경우 center 에 트리 이미지를 넣어주었고, SideBar의 크기만큼 width를 빼주었습니다.

🎄 Object 그리기

onMouseMove

//Wrapper component
onMouseMove={(e) => {
  if(selectedObj!=='') {
    setMousePosition({ ...mousePosition, positionX: e.clientX, positionY: e.clientY });
  }
}}

선택된 object가 있을 경우에만 mousePosition에 마우스 위치 할당해줍니다.

SelectedObj

마우스를 따라다니는 컴포넌트입니다.

{selectedObj !== "" &&
mousePosition.positionX &&
mousePosition.positionY ? (
  <SelectedObj
    backgroundImg={selectedObj}
    style={{
      position: "absolute",
      left: mousePosition.positionX,
      top: mousePosition.positionY,
    }}
    onClick={(e) => {
      setSelectedObj("");
      drawObject({ positionX: e.clientX, positionY: e.clientY });
    }}
  />
) : null}

const SelectedObj = styled.div<ObjContainerProps>`
  width: 100px;
  height: 100px;
  background-image: url(${(props) => props.backgroundImg});
  background-size: cover;
  transform: translate(-50%, -50%);
  overflow: visible;
`;

selectedObj state 값이 ''이 아니고(SideBar에서 object 클릭이 일어났을 경우),
mousePosition의 positionX와 positionY가 모두 null이 아닐 경우 생성됩니다.
마우스에 object의 중앙이 오도록 transform: translate(-50%, -50%)를 해주었습니다.

클릭이 발생할 경우 해당 위치를 drawObject의 parameter로 전달해주고,
selectedObj state를 초기화해줍니다.

drawObject()

  const drawObject = (mouseEndPosition: mousePosition) => {
    const canvasCur = canvasRef.current as HTMLCanvasElement;
    const ctx = canvasCur.getContext("2d");
    const objImage = new Image();
    objImage.src = selectedObj;
    if(ctx === null) return ;
    if(!mouseEndPosition.positionX) return ;
    if(!mouseEndPosition.positionY) return ;
    ctx.drawImage(objImage,mouseEndPosition.positionX-50,mouseEndPosition.positionY-50, 100, 100);
  }

object의 크기를 100px로 할당해주었기 때문에,
클릭한 지점에 object의 중앙이 그려지도록 x와 y위치에 각각 -50씩 해주었습니다.


🎄 완성

0개의 댓글