본문 바로가기

Web

CSS 3D

개요.

카드를 웹사이트에서 구현한다고 했을 때 뒤집는 애니메이션이 필요한 경우가 있다. 

그냥 앞뒤가 변화는 것보다 3D로 뒤집히는 것이 사용자가 봤을 때 자연스럽고 완성된 요소라고 생각할 수 있다. 

뒤집는 것을 생각할 때 보통 해결 하는 방법은 transform의 rotate를 사용하는 것이다. 

하지만 일반적으로 rotate를 사용해서 45도를 바꾸면 아래와 같이 나오게 된다. 

그냥 네모 박스가 쪼그라든 것 같은 형식이다. 

이 이유는 노란색 박스 즉, 카드를 포함하고 있는 부모 요소가 3차원 공간이어야 내부 요소도 3D로 동작한다. 

 

3D 카드 뒤집기. 

<body>
    <div class="world">
      <div class="card">CARD</div>
    </div>
</body>

 

먼저 부모 요소와 자식 카드 박스를 만들어준다. 

부모 박스가 위 이미지의 노란색 영역이고 카드는 붉은색 카드를 나타낸다. 

.world {
    display: flex;
    align-items: center;
    justify-content: center;

    width: 80vw;
    height: 80vh;

    background: yellow;
}
.card {
    display: flex;
    align-items: center;
    justify-content: center;

    width: 100px;
    height: 150px;

    margin: 1em;
    border-radius: 0.5rem;
    background: red;

    font-size: 1.5rem;
    color: white;
}

 

각각의 CSS 스타일! 

 

막간으로 rem은 html 전체의 px을 기준으로 나타나는 값이고 em은 해당 요소의 폰트 크기를 기준으로 나타낸다. 

html의 기본 폰트 크기가 20px이라고 했을 때 1rem은 20px 그리고 1rem을 가지고 있는 요소의 0.5em은 10px이다. 

이것은 폰트의 크기에 따라 유동적으로 값을 주기 위해서 사용한다. 

 

필수는 아니고 디자인에 따라서 사용하고 말고를 정하면 된다. 

.card {
    // ...
    transform: rotateY(45deg);
}

 

이제 뒤집는 효과를 위해서 카드에게 rotate를 45도 주었다. 

하지만 결과는 이미지에서 본 것과 똑같이 쪼그라든 붉은색 네모이다. 

 

이것을 3D로 만들기 위해서 부모 요소를 3차원으로 변경해줘야 한다. 

.world {
    // ...
    perspective: 500px;
}

 

새롭게 등장하는 perspective 속성이다.

이것은 사용자의 시각에서 값으로 주는 부분만큼의 원근감을 나타낸다.

실제로도 우리가 사물을 볼 때 가까이에 있는 것과 멀리 있는것의 원근감은 다르기 때문에 큰 값을 준다면

더 멀리 있다고 판단이 되므로 효과가 작게 느껴질 것이다. 

 

정리하면 수치가 작으면 3D 효과가 극적으로 나타나고, 클 수록 완만하게 나타난다.  

 

적용된 것을 확인하면 제대로 적용되어 있다. 하지만 3개의 카드를 넣었을 때 각각이 다른 회전 비율을 가지고 있는

것을 알 수 있는데, 위치에 따라 원근감이 달라져서 다르게 나타나는 것이다. 

 

이렇게 나타나는 이유는 보이지는 않지만 원근감의 변화가 소실점을 기준으로 변경되기 때문이다. 

기본 값은 center로 눈에 보이지 않지만 가운데에 소실점이 존재한다. 

 

perspective-origin 속성을 사용하면 해당 소실점의 위치를 변경할 수 있다. 

 

이것을 쉽게 해결하기 위해서는 world에서 있던 perspective 속성을 각각의 카드에게 제공 해줘야 한다.

그렇게 되면 카드마다의 소실점이 생기기 때문에 각도의 차이가 없어진다.  

.card {
    // ...

    transform: perspective(500px) rotateY(45deg);
}

 

world에 있던 perspective 속성을 제거하고 card의 transform에서 동일한 속성의 함수로 제공해 주었다. 

 

이제 결과물을 보면 모든 카드가 동일하게 회전하고 있는 것을 볼 수 있다. 

 

애니메이션 만들기.

이제 마우스를 올리면 카드가 뒤집어지는 애니메이션을 한번 만들어보자

뒤집는 애니메이션을 별로 어렵지 않다. 마우스 호버 했을 때 rotateY는 주면 그만! 

<!DOCTYPE html>
<html lang="ko">
  <head>
    <style>
      // ...
      
      .card {
        // ...

        transition: 1s;
        transform: rotateY(0);
      }

      .world:hover .card {
        transform: rotateY(180deg);
      }
    </style>
  </head>
  <body>
    <div class="world">
      <div class="card">CARD</div>
    </div>
  </body>
</html>

 

노란색 영역에 마우스가 있으면 카드가 뒤집히는 애니메이션이 만들어졌다. 매끄러운 애니메이션을 위해서 

transition으로 1s를 설정해주었다. 

 

이때 transform: rotateY(0)은 설정하지 않아도 애니메이션에는 문제없지만 초기 세팅을 해준다면 실제 애니메이션이

발생했을 때 성능상 이점이 있다고 한다. 

 

완성한 애니메이션을 보면 뒤집혔을 때 CARD가 뒤집혀서 나오는 것을 볼 수 있다. 

하지만 일반적인 카드는 뒤집히면 뒷면이 따로 존재하고 있다. 이것을 동일하게 만들어보자. 

 

먼저 베이스 코드를 조금 수정해줘야 하는데 :

  <body>
    <div class="world">
      <div class="card">
        <div class="card-side card-side-front">F</div>
        <div class="card-side card-side-back">B</div>
      </div>
    </div>
  </body>

 

앞면을 card-side-front 클래스를 통해서 나타내고 뒷면을 card-side-back을 통해서 만들어줬다. 

일반적으로는 F 부분이 나타나고 호버를 했을 땐 B가 출력되는 것을 목표로 만들 계획이다. 

 

일단 기본적인 애니메이션과 스타일을 주었다 :

  .card {
    position: relative;

    width: 100px;
    height: 150px;

    margin: 1em;

    transition: 1s;
    transform: rotateY(0);
  }

  .world:hover .card {
    transform: rotateY(180deg);
  }

  .card-side {
    position: absolute;
    top: 0;
    left: 0;

    width: 100%;
    height: 100%;

    display: flex;
    align-items: center;
    justify-content: center;

    border-radius: 0.5rem;

    font-size: 1.5rem;
    color: white;
  }

  .card-side-front {
    z-index: 1;

    background: red;
  }

  .card-side-back {
    background: blue;
  }

 

폰트라던지 정렬 등은 card-side에게 넘겨주고 앞면은 붉은색, 뒷면은 파란색으로 설정해 주었다. 

그리고 앞면과 뒷면이 겹쳐야하기 때문에 card에게 position: relative를 주고 front에게 z-index를 1 주었다. 

 

이론상 완벽한 카드인데 전혀 의도한 것처럼 B 라는 뒷면이 화면에 나타나지 않는 것을 확인할 수 있다. 

 

이유는 노란색 영역은 perspective 옵션을 통해서 3차원이 되었지만 안에 있는 card는 속성을 가지고 있지 

않기 때문이다.

 

이것을 해결하기 위해서 card 역시 3차원으로 설정해줄 필요가 있다. 

  .card {
    // ...

    transform-style: preserve-3d;
  }

 

transform-style의 preserve-3d는 영역을 3차원으로 바꿔주는 속성이다. 

추가로 카드에게도 뒷면은 보이지 않게끔 설정해줄 필요가 있다. 

 

transform-style만 설정해주면 3차원 설정을 되지만 카드를 뒤집었을 때 앞면의 뒷부분이 노출되는 것은 동일하다. 

그러니 제대로 뒷면을 나오게 하려면 뒷 화면은 노출되지 않게 설정을 해주어야 한다. 

 

  .card-side {
    // ...

    backface-visibility: hidden;
  }

 

backface-visibility 속성을 사용하면 요소의 뒷면이 사용자에게 보일지 말지를 설정할 수 있다. 

hidden 속성을 준다면 요소의 뒷부분이 사용자에게 보이지 않는다. 

 

이것으로 rotateY(180deg)를 통해 뒷면이 노출되면 사용자에게 나오지 않는 것이다. 

추가적으로 뒷면인 B 부분도 같이 180도 돌아가기 때문에 뒷면이 보이지 않는다. 

그러므로 뒷면만 따로 180도를 돌려주면 뒤집혔을 때 뒷면은 정면이 되므로 화면에 제대로 노출된다. 

  .card-side-back {
    transform: rotateY(180deg); 

    background: blue;
  }

 

이렇게 설정해준다면 뒤집히면 정면을 보고 있는 뒷 배경이 화면에 노출될 것이다. 

 

완성된 부분은 조금의 디테일을 위해서 수정이 했는데 뒤집히는 것을 자연스럽게 하기 위해서 카드에서 

transform-origin을 left로 줘서 뒤집는 모션을 만들어줬고 이때 뒤집히는 각도를 180deg가 아닌 -180deg를 줘서 

매끄럽게 뒤집어지도록 변경하였다. 

<!DOCTYPE html>
<html lang="ko">
  <head>
   // ...

    <style>
      .world {
        display: flex;
        align-items: center;
        justify-content: center;

        width: 80vw;
        height: 80vh;

        background: yellow;

        perspective: 500px;
      }
      .card {
        position: relative;

        width: 100px;
        height: 150px;

        margin: 1em;

        transition: 1s;
        transform: rotateY(0);

        transform-style: preserve-3d;
        transform-origin: left;
      }

      .world:hover .card {
        transform: rotateY(-180deg);
      }

      .card-side {
        position: absolute;
        top: 0;
        left: 0;

        width: 100%;
        height: 100%;

        display: flex;
        align-items: center;
        justify-content: center;

        border-radius: 0.5rem;

        font-size: 1.5rem;
        color: white;

        backface-visibility: hidden;
      }

      .card-side-front {
        z-index: 1;

        background: red;
      }

      .card-side-back {
        transform: rotateY(180deg);

        background: blue;
      }
    </style>
  </head>
  <body>
    <div class="world">
      <div class="card">
        <div class="card-side card-side-front">F</div>
        <div class="card-side card-side-back">B</div>
      </div>
    </div>
  </body>
</html>

 

전체 코드 !

 

문제 해결하기 

지금까지의 작업은 모두 크롬을 기준으로 작업했다. 하지만 브라우저는 많고 브라우저마다 사용할 수 없는

속성이 있다. 

 

해당 기능이 정상 동작하기 위해서 브라우저별로 수정해 보자. 

iOS 

backface-visibility 속성이 크롬에서는 제대로 동작하지만 iOS 환경에서는 제대로 동작하지 않는 문제가 있다. 

다행히 이것은 해결하기 간단한데, iOS에 맞춰서 스타일 속성을 추가해 주면 된다. 

  .card-side {
    // ...

    -webkit-backface-visibility: hidden;
    backface-visibility: hidden;
  }

 

-webkit- 키워드를 통해서 iOS에서 지원하는 backface-visibility 속성을 사용하면 문제없이 지원한다. 

 

IE

 

IE11에서는 transform-style: preserve-3d 속성을 지원하지 않는다... 

그래서 애니메이션 효과를 봤을 때도 의도했던 애니메이션이 나오지 않는 것을 확인할 수 있다. 

 

이것을 해결하기 위해서 구조와 스타일을 변경해야 할 필요가 있다. 

  <body>
    <div class="world">
      <div class="card-side card-side-front">F</div>
      <div class="card-side card-side-back">B</div>
    </div>
  </body>

 

먼저 부모 요소였던 card에서 뒤집는 부분이 자식에게 전파가 안 되는 문제가 있었으니 변경해서 world 아래에 

바로 앞면과 뒷면을 배치했다.

 

그리고 위치를 잡아주던 card가 없어졌으므로 위치와 애니메이션 딜레이 요소도 다시 잡아줬다 : 

  .card-side {
    position: absolute;
    top: 50%;
    left: 50%;

    margin-top: -75px;
    margin-left: -50px;

    width: 100px;
    height: 150px;

    display: flex;
    align-items: center;
    justify-content: center;

    border-radius: 0.5rem;

    font-size: 1.5rem;
    color: white;

    transition: 1s;

    -webkit-backface-visibility: hidden;
    backface-visibility: hidden;
  }

 

가운데 정렬을 위해서 transform의 translate을 사용해도 되지만 transform에서는 rotate를 사용하고 있고 이어 수정할

위치 변경하는 부분도 있어서 복잡해질 수 있는 부분이라 margin을 통해서 가운데 정렬을 해주었다. 

 

  .card-side-front {
    z-index: 1;
    transform: rotateY(0);

    transform-origin: left;

    background: red;
  }

  .card-side-back {
    transform-origin: right;

    transform: translateX(-100px) rotateY(180deg);

    background: blue;
  }
  
  .world:hover .card-side-front {
    transform: rotateY(-180deg);
  }

  .world:hover .card-side-back {
    transform: translateX(-100px) rotateY(0deg);
  }

 

마지막으로 애니메이션 부분도 변경되었다. 

우선 뒷면은 기본적으로 180deg가 미리 되어 있었다는 부분 때문에 그대로 작업하면 애니메이션이 이상하게 

깨지는 문제가 있었다. 

 

그래서 중심점을 right로 바꾸고 180deg를 한 다음에 호버를 하면 0으로 바꿔서 다시 원래자리로 돌아오게 했다. 

이렇게 하면 앞면은 0deg에서 180deg로 바뀌면서 왼쪽으로 넘어가고 

뒷면은 180deg에서 다시 0으로 바뀌니깐 왼쪽으로 넘어가면서 동일한 애니메이션을 만들 수 있었다. 

 

하지만 뒷면은 미리 뒤집어지므로 100px 오른쪽으로 이동했으니 이것을 앞면과 맞추기 위해서 -100px translate으로

이동시켜주어서 동일한 애니메이션을 만들어주었다. 

 

다른 브라우저에서 동작하지 않는 문제를 해결하기 위해서 꽤나 복잡한 작업을 했지만 이것으로 iOS와 IE, 크롬에서

동작하는 카드 뒤집기 애니메이션을 만들 수 있었다. 

 

전체 코드 

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>3D3</title>

    <style>
      .world {
        position: relative;

        display: flex;
        align-items: center;
        justify-content: center;

        width: 80vw;
        height: 80vh;

        background: yellow;

        perspective: 500px;
      }

      .world:hover .card-side-front {
        transform: rotateY(-180deg);
      }

      .world:hover .card-side-back {
        transform: translateX(-100px) rotateY(0deg);
      }

      .card-side {
        position: absolute;
        top: 50%;
        left: 50%;

        margin-top: -75px;
        margin-left: -50px;

        width: 100px;
        height: 150px;

        display: flex;
        align-items: center;
        justify-content: center;

        border-radius: 0.5rem;

        font-size: 1.5rem;
        color: white;

        transition: 1s;

        -webkit-backface-visibility: hidden;
        backface-visibility: hidden;
      }

      .card-side-front {
        z-index: 1;
        transform: rotateY(0);

        transform-origin: left;

        background: red;
      }

      .card-side-back {
        transform-origin: right;

        transform: translateX(-100px) rotateY(180deg);

        background: blue;
      }
    </style>
  </head>
  <body>
    <div class="world">
      <div class="card-side card-side-front">F</div>
      <div class="card-side card-side-back">B</div>
    </div>
  </body>
</html>
반응형

'Web' 카테고리의 다른 글

3D 애니메이션 심화  (2) 2024.09.08
CSS 레이아웃 시스템의 변화  (1) 2024.06.05
CSS 선택자  (2) 2024.05.05
Animation - frame by frame  (3) 2024.05.01
Animation  (5) 2024.04.28