[CATSPOT]React + Recoil 기반 계단 애니메이션 구현

2025. 3. 27. 15:26프론트엔드/REACT

728x90

컴포넌트 정의 및 내부 상태 초기화

컴포넌트 Stair는 totalFloors(전체 층 수)를 prop으로 받아 동적으로 계단을 생성합니다.

const Stair = ({ totalFloors }) => {
  // 전역 상태: 선택된 층 (Recoil)
  const [selectedFloor, setSelectedFloor] = useRecoilState(currentFloorState);
  // 애니메이션 진행 여부 확인
  const [isAnimating, setIsAnimating] = useState(false);

설명:

  • selectedFloor는 현재 선택된 층을 나타내며, Recoil의 전역 상태로 관리
  • isAnimating 상태는 애니메이션이 진행 중인지 여부를 판단하여 중복 실행을 방지

층 선택 및 애니메이션 시작 함수

사용자가 층을 클릭하면 애니메이션을 시작하는 함수입니다.

  // 사용자가 층을 클릭했을 때 호출되는 함수
  const handleClick = (floor) => {
    if (floor !== selectedFloor && !isAnimating) {
      setIsAnimating(true);
      animateFloorChange(selectedFloor, floor);
    }
  };

설명:

  • 조건 확인: 선택된 층과 클릭한 층이 다르고, 현재 애니메이션이 진행 중이지 않을 때만 애니메이션을 시작
  • animateFloorChange 함수를 호출하여 현재 층에서 목표 층까지 이동하는 애니메이션을 진행

단계별 층 이동 애니메이션 함수

실제 애니메이션을 구현하는 함수로, 일정 간격마다 층 정보를 업데이트합니다.

  // 층 이동을 단계적으로 구현하는 함수
  const animateFloorChange = (startFloor, targetFloor) => {
    const direction = targetFloor > startFloor ? 1 : -1; // 올라가는지 내려가는지 판단
    let currentFloor = startFloor;

    const stepAnimation = setInterval(() => {
      currentFloor += direction;
      setSelectedFloor(currentFloor);

      if (currentFloor === targetFloor) {
        clearInterval(stepAnimation);
        setIsAnimating(false); // 애니메이션 종료
      }
    }, 200); // 0.2초 간격으로 층 변경
  };

설명:

  • 방향 결정: 목표 층이 현재 층보다 높으면 1, 낮으면 -1로 이동 방향을 설정
  • setInterval: 200ms마다 currentFloor 값을 한 단계씩 증가(또는 감소)시키고, 해당 값을 Recoil 상태에 업데이트
  • 애니메이션 종료: 목표 층에 도달하면 clearInterval로 반복을 멈추고, 애니메이션 상태를 false로 전환

컴포넌트 초기화 및 동적 층 생성

전체 층 수가 변경될 때마다 기본 상태로 초기화하고, 각 층을 동적으로 렌더링합니다.

  // totalFloors 값이 변경될 때마다 selectedFloor 초기화
  useEffect(() => {
    setSelectedFloor(1);
  }, [totalFloors]);

  // totalFloors에 따른 층 배열 생성
  const floors = Array.from({ length: totalFloors }, (_, index) => index + 1);

설명:

  • useEffect를 사용해, 건물(또는 총 층 수)이 변경되면 자동으로 선택된 층을 1층으로 초기화합니다.
  • Array.from을 통해 1부터 totalFloors까지의 숫자 배열을 생성하여 렌더링에 사용합니다.

6. 렌더링 및 조건부 아이콘 표시

생성한 층 배열을 기반으로 각 계단 UI를 렌더링하고, 선택된 층에만 아이콘을 표시합니다.

  return (
    <div className="stair-container">
      {floors.map((floor, index) => (
        <div
          key={index}
          className="stair-step"
          style={{ height: `${(index + 1) * (252 / totalFloors)}px`, width: '100%' }}
          onClick={() => handleClick(floor)}
        >
          <p className='stair-floor'>{floor}F</p>
          <div className="step-decorator"></div>
          {selectedFloor === floor && (
            <div className="icon-container">
              <img
                src="/assets/icon.svg"
                alt="Icon"
                className="icon"
              />
            </div>
          )}
        </div>
      ))}
    </div>
  );
};

export default Stair;

설명:

  • 동적 스타일: 각 계단(stair-step)의 높이를 계산해 동적으로 적용
  • 클릭 이벤트: 각 계단에 onClick 이벤트를 붙여, 사용자가 해당 층을 클릭하면 handleClick 함수가 호출
  • 조건부 렌더링: 현재 선택된 층과 일치하는 경우에만 아이콘 컨테이너(icon-container)가 렌더링되어, 애니메이션 효과가 적용된 아이콘을 표시

CSS를 통한 애니메이션 효과

CSS 파일(stair.css)에서 계단과 아이콘에 적용된 스타일과 애니메이션을 살펴봅니다.

.stair-container {
  margin-top: 70px;
  display: flex;
  flex-direction: row; /* 계단이 한 줄로 정렬 */
  align-items: flex-end; /* 바닥 정렬 */
  width: 100%;
  height: 252px;
  padding: 0px 25px;
}

.stair-step {
  display: flex;
  justify-content: center;
  align-items: baseline;
  background-color: #182981;
  color: white;
  font-size: 20px;
  font-family: 'Adlam Display', sans-serif;
  font-weight: 400;
  text-align: center;
  border-radius: 5px;
  position: relative;
  box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.5); /* 입체감 추가 */
  background: linear-gradient(145deg, #47578d, #182981);
}

.step-decorator {
  position: absolute;
  top: -5px;
  left: 0;
  width: 100%;
  height: 16px;
  background-color: #B9BFE3;
  border-top-left-radius: 10px;
}

.icon-container {
  position: absolute;
  top: -40px; /* 아이콘을 계단 위에 위치 */
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  transition: top 0.5s ease-in-out; /* 위치 변경 시 애니메이션 효과 */
}

.stair-floor {
  margin-top: 10px;
}

.icon {
  width: 120px; /* 아이콘 크기 조절 */
  margin-bottom: 20px;
  animation: bounce 1.6s ease-in-out infinite; /* 아이콘 bounce 애니메이션 */
}

@keyframes bounce {
  0%, 20%, 50%, 80%, 100% {
    transform: translateY(-20px);
  }
  40% {
    transform: translateY(-110px);
  }
  60% {
    transform: translateY(-70px);
  }
}

설명:

  • .stair-container & .stair-step:
    계단 전체 컨테이너와 개별 계단의 배치, 크기, 색상, 그림자 등 기본 스타일을 정의합니다.
  • .step-decorator:
    각 계단의 상단에 위치한 장식 요소로, 입체감을 더해줍니다.
  • .icon-container & .icon:
    아이콘이 계단 위에 자연스럽게 등장하며, transition과 @keyframes bounce를 활용해 애니메이션 효과를 부여(통통 튀는 효과로 동적이 사용자 경험 추가)

 

728x90