개발/개발로그

[React] 간단한 성냥퍼즐 웹 서비스 만들기 4일차 (react-konva)

seungho-dev 2024. 11. 15. 19:35

오늘은 드디어 핵심 기능인 퍼즐 시스템을 만들어 보려고 한다

너무 재밌겠다 바로 가자~!

 

개인적인 공부를 위해 작성하는 블로그입니다. 혹시라도 잘못되거나 부족한 부분이 있다면 댓글로 알려주시면 감사하겠습니다.

💡 핵심 기능 구현

  • 성냥개비 드래그 & 드롭 (오늘 할 것)
  • 성냥개비 회전
  • 퍼즐 정답 검증 시스템

 

✨ React-Konva 설치

npm install react-konva konva

일단 제일 중요한 기능인 캔버스에서 성냥개비를 회전하고 드래그 & 드롭하는 기능을 순수 js로 구현해보고 싶은 마음은 굴뚝같지만 누군가 좋은 라이브러리를 만들어주셔서 예의상 사용하기로 했다. 🥰

 

오늘은 간단하게 konva.js를 이용해서 이미지를 드래그 & 드롭할 수 있게 만들어볼 거다. 우선 성냥의 구조체를 생각해 보았다. 가볍게 생각해 봤을 때 id값이랑 x, y 그리고 회전값만 있으면 될 것 같다.

 

우선 kanvaCanvas 컨포넌트를 만들어주고 react-konva에서 Stage, Layer, Rect를 가져와준다.

matchsitck 더미데이터를 만들어주고 <Stage>는 canvas라고 생각하면 된다 그 안에 <Layer>를 만들어주고 내부에 우선 이미지 대신 Rect(사각형)을 만들어 주었다. 드래그하려면 정말 간단하게도 'draggable'만 추가해 주면 이렇게 바로 가능해진다. 

import { useState } from "react";
import { Stage, Layer, Rect } from "react-konva";

export default function PuzzleCanvas() {
  const [matchsticks, setMatchsticks] = useState([
    { id: "1", x: 50, y: 50, angle: 0 },
    { id: "2", x: 200, y: 100, angle: 0},
  ]);

  return (
    <Stage width={window.innerWidth} height={window.innerHeight}>
      <Layer>
        {matchsticks.map((stick) => (
          <Rect
            key={stick.id}
            x={stick.x}
            y={stick.y}
            width={100}
            height={20}
            fill={'red'}
            draggable
          />
        ))}
      </Layer>
    </Stage>
  );
}

 

이제 Rect 대신 Image로 바꿔주고 이미지 링크를 넣어줬는데 바로 오류가 발생했는데 찾아보니 Konva는 HTML5 Canvas를 기반으로 작동하기 때문에, 이미지를 렌더링 하려면 HTMLImageElement 객체를 사용해야 해서 단순히 이미지의 경로(문자열)를 전달하면 React와 Konva가 이를 처리하지 못하고 오류가 발생한다 따라서 이미지가 브라우저에서 완전히 로드된 이후 그려지도록 해야 한다.

React의 useEffect()로 브라우저가 랜더링 된 이후 useRef()에 이미지를 연결함으로 이 문제를 해결하였다.

 

1. useEffect()는 브라우저가 랜더링된 이후(commit phase 이후) 실행되므로 여기서 이미지를 로드하는 작업을 처리

2. window.Image() 객체를 사용해 이미지를 로드하고, onload 콜백을 통해 이미지가 완전히 로드된 시점을 감지

3. 이미지가 로드된 후 useRef에 연결하여 Konva가 이를 사용할 수 있게한다. 마지막으로 화면에 다시 그려줘야 하기 때문에 setMatchsticks 전체 배열을 ...(spread 연산자)를 사용해 변경해준다.

 

import { useState, useRef, useEffect } from "react";
import { Stage, Layer, Image, Rect } from "react-konva";

export default function PuzzleCanvas() {
  const [matchsticks, setMatchsticks] = useState([
    { id: "1", x: 50, y: 50, angle: 10 },
    { id: "2", x: 200, y: 100, angle: 30},
    { id: "3", x: 100, y: 150, angle: 180},
    { id: "4", x: 150, y: 100, angle: 270},
  ]);

  const imageRef = useRef(null);

  useEffect(() => {
    const img = new window.Image();
    img.src = "/matchstick.webp"; // 이미지 경로
    img.onload = () => {
      imageRef.current = img;
      setMatchsticks((sticks) => [...sticks]); // 이미지 로드 후 리렌더링
    };
  }, []);

  return (
    <Stage width={window.innerWidth} height={window.innerHeight}>
      <Layer>
        {matchsticks.map((stick) => (
          <Image
            key={stick.id}
            x={stick.x}
            y={stick.y}
            rotation={stick.angle}
            width={20}
            height={100}
            image={imageRef.current}
            draggable
          />
        ))}
      </Layer>
    </Stage>
  );
}

 

 

이제 잘나오는 걸 볼 수 있다.

 

읽어주셔서 감사합니다