개발/개발로그

[React] 간단한 성냥퍼즐 웹 서비스 만들기 5일차 (Konva.Transformer)

seungho-dev 2024. 11. 18. 22:26

오늘은 핵심 기능구현 두 번째로 드래그 & 드롭에 이어

성냥개비를 회전하는 기능을 만들어 보려고한다

오늘도 재밌게 만들어보자~!

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

 

💡 핵심 기능 구현

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

 

💁🏻 현재 선택된 성냥개비 구현

우선 Image에 onClick을 추가하고 클릭했을때 handleSelect()를 통해 현재 선택된 성냥의 id값을 저장한다.

import { useRef, useEffect, useState } from "react"
import { Stage, Layer, Image, Transformer } 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 [selectedMatchstick, setSelectedMatchstick] = useState(null)

  const imageRef = useRef(null);

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

  const handleSelect = (id) => {
    setSelectedMatchstick((current) => (current === id ? null : id));
  };

  return (
    <Stage
      ref={stageRef}
      width={window.innerWidth}
      height={window.innerHeight}
    >
      <Layer>
        {matchsticks.map((stick) => (
          <Image
            key={stick.id}
            id={stick.id}
            x={stick.x}
            y={stick.y}
            rotation={stick.angle}
            width={18}
            height={150}
            image={imageRef.current}
            draggable
            onTap={() => handleSelect(stick.id)} // 모바일 지원
            onClick={() => handleSelect(stick.id)} // 선택
          />
        ))}
      </Layer>
    </Stage>
  )
}

모바일에서도 클릭을 지원하려면 onTap을 같이 넣어주면 된다.

👨🏻‍💻 Transformer로 회전 구현

선택되었을 때와 성량의 위치가 바뀌었을때 useEffect를 통해 선택된 성냥을 찾아서 tr.node() tr.getLayer().batchDraw()로 Transformer를 업데이트 해준다.

export default function PuzzleCanvas() {

    const transformerRef = useRef(null);
    const stageRef = useRef(null);
    
  	(생략)
    
   // Transformer 업데이트
  useEffect(() => {
    const tr = transformerRef.current;
    if (tr) {
      if (selectedMatchstick) {
        const selectedNode = stageRef.current.findOne(`#${selectedMatchstick}`);
        if (selectedNode) {
          tr.nodes([selectedNode]) // 선택된 노드에 Transformer 적용
          tr.getLayer().batchDraw()
        }
      } else {
        tr.nodes([]) // 선택 해제 시 Transformer 초기화
      }
    }
  }, [selectedMatchstick, matchsticks])
  
  (생략)
  
  return (
    <Stage
      ref={stageRef}
      width={window.innerWidth}
      height={window.innerHeight}
    >
      <Layer>
        {matchsticks.map((stick) => (
          <Image
            (생략)
            onTap={() => handleSelect(stick.id)} // 모바일 지원
            onClick={() => handleSelect(stick.id)} // 선택
            onDragMove={(e) => handleDragMove(e, stick.id)} // 이동
          />
        ))}
        {/* Transformer */}
        <Transformer
          ref={transformerRef}
          rotationSnaps={[0, 90, 180, 270]} // 회전 스냅
          anchorSize={10} // 앵커 크기
          anchorCornerRadius={3}
          centeredScaling={true}
          resizeEnabled={false} // 크기 조정 비활성화
        />
      </Layer>
    </Stage>
  )
}

그럼 이렇게 바운딩박스와 함께 회전이 가능해진다

 

읽어주셔서 감사합니다.