본문 바로가기
Next.js/개발 노트

[React/Next.js] 클릭 또는 몇 초 후에 카드 뒤집기 효과 (card flip)

by tokkiC 2023. 12. 31.


몇 초 후에 자동으로 카드를 뒤집고 클릭시에도 카드를 뒤집는 효과이다

보통 이렇게 카드를 뒤집는 것을 card flip 이라고 하고, 검색으로 쉽게 구현이 가능한 효과다.
많은 사람들이 over 시 뒤집히도록 css로 hover 시 변화하도록 많이 구현하지만, 나는 hover가 아니라 클릭 시 뒤집히길 바라고, 클릭하지 않아도 몇 초 후에 자동으로 뒤집히게 구현해보고 싶었다.

핵심 원리를 요약하자면,

  1. CSS transition과 transform-style: preserve-3d;을 사용하여 카드 요소의 3D 회전 효과를 구현
  2. React의 useState와 useEffect로 카드의 뒤집힘 상태(isFlipped)를 관리하고, 시간 간격이나 클릭 이벤트에 따라 상태를 변경
  3. isFlipped 상태에 따라 flipped 클래스를 조건부로 적용하여 카드 뒤집기 효과를 활성화

이렇게 된다.

아래의 코드의 각 줄마다 상세 설명을 주석으로 남겨두었다.

Next.js 14와 타입스크립트를 사용하였지만, React에도 "use client" 를 제거하면 그대로 사용 가능하다.

"use client";

import Image from "next/image";
import MultiTypingEffect from "../MultiTypingEffect/MultiTypingEffect";
import useDeviceType from "@/app/hooks/useDeviceType";
import style from "./About.module.css";
import { useEffect, useState } from "react";

const aboutTexts = [
  `문자열1`,`문자열2`
];

export default function About() {
  const deviceType = useDeviceType();
  const [isFlipped, setIsFlipped] = useState(false);

  useEffect(() => {
    const interval = setInterval(() => {
      setIsFlipped((prev) => !prev);
    }, 3000);

    return () => clearTimeout(interval);
  }, [isFlipped]);

  const handleImageClick = () => {
    setIsFlipped(!isFlipped);
  };

  return (
    <section className={`${style.container} scroll-point`} id="about">
      <div className={`w-full min-h-[28rem] flex justify-center`}>
        {/* 좌측 텍스트 영역 */}
        <div className={style.textWrapper}>
          <h2 className={style.head2}>About</h2>
          <div className=" text-2xl mb-8">
            <MultiTypingEffect texts={aboutTexts} />
          </div>
        </div>

        {/* 우측 사진 영역 */}
        {deviceType === "desktop" && (
          <div className={style.imageWrapper} onClick={handleImageClick}>
            <div
              className={`${style.flipCard} ${isFlipped ? style.flipped : ""}`}
            >
              <div className={`${style.front}`}>
                <Image
                  className={`${style.profileImg}`}
                  src="/앞면_이미지.jpg"
                  alt="앞면에 보일 이미지"
                  fill
                />
              </div>
              <div className={`${style.back}`}>
                <Image
                  className={`${style.profileImg}`}
                  src="/뒷면_이미지.jpg"
                  alt="뒷면에 보일 이미지"
                  fill
                />
              </div>
            </div>
          </div>
        )}
      </div>
    </section>
  );
}
.container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100dvh;
  flex-direction: column;
}

.head2 {
  font-size: 4rem;
  margin-bottom: 2rem;
}

.textWrapper {
  width: 30rem;
  display: flex;
  flex-direction: column;
  min-height: 12rem;
}

@media screen and (min-width: 450px) {
  .container {
    padding-left: 2rem;
    padding-right: 2rem;
  }

  .head2 {
    font-size: 6rem;
    font-weight: 800;
  }

  .textWrapper {
    width: 30rem;
    min-height: 25rem;
  }
}

@media screen and (min-width: 1024px) {
  .container {
    padding-left: 2rem;
    padding-right: 2rem;
    max-width: 120rem;
  }

  .head2 {
    font-size: 8rem;
    font-weight: 800;
    margin-bottom: 1rem;
  }

  .textWrapper {
    width: 40rem;
    min-height: 33rem;
  }

  .imageWrapper {
    width: fit-content;
    display: flex;
    justify-content: center;
    align-items: center;
    perspective: 1000px;
  }

  .flipCard {
    transition: 0.8s;
    transform-style: preserve-3d;
    position: relative;
    width: 240px;
    height: 240px;
  }

  .profileImg {
    border-radius: 0.5rem;
    border: 2px solid white;
    object-fit: cover;
  }

  .front,
  .back {
    backface-visibility: hidden;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
  }

  .back {
    transform: rotateY(180deg);
  }

  .flipped {
    transform: rotateY(180deg);
  }
}

댓글