[CSS] 3d-carousel

Main·2023년 3월 24일
1

'회전목마'라는 뜻으로 슬라이드쇼와 같은 방식으로 콘텐츠를 표시하는 UX 구성 요소를 의미합니다.

회전목마


css와 javascript를 이용하여 직접 3d-carousel를 구현합니다.

💡 구현 포인트

  • prespective 속성과 transform-style: preserve-3d 속성
  • transformZ 길이 적용

👇 각 css 속성에 대한 설명은 아래 mdn 문서를 참고해주세요.
prespective
transform-style
transform

<div class="carousel">
        <div class="carousel-card">Card1</div>
        <div class="carousel-card">Card2</div>
        <div class="carousel-card">Card3</div>
        <div class="carousel-card">Card4</div>
        <div class="carousel-card">Card5</div>
        <div class="carousel-card">Card6</div>
        <div class="carousel-card">Card7</div>
        <div class="carousel-card">Card8</div>
        <div class="carousel-card">card9</div>
</div>

2 ) 원근감 효과를 위해 carousel의 부모요소를 만들고 perspective 속성을 부여

 <div class="scene">
      <div class="carousel">
        <div class="carousel-card">Card1</div>
        <div class="carousel-card">Card2</div>
        <div class="carousel-card">Card3</div>
        <div class="carousel-card">Card4</div>
        <div class="carousel-card">Card5</div>
        <div class="carousel-card">Card6</div>
        <div class="carousel-card">Card7</div>
        <div class="carousel-card">Card8</div>
        <div class="carousel-card">card9</div>
      </div>
 </div>
.scene {
  width: 210x;
  height: 140px;
  position: relative;
  /* 원근감을 위해 */
  perspective: 1800px;
  margin: 0 auto;
  margin-top: 100px;
}

3 ) carousel의 자식요소에 3d 효과를 주기위해 perserve-3d 속성을 부여

.carousel {
  width: 100%;
  height: 100%;
  position: absolute;
  /* perspective가 적용된 자식 요소들에 3D 효과 원근감*/
  transform-style: preserve-3d; 
  transition: all .5s;
}
.carousel-card {
  position: absolute;
  /* 셀 배치를 약간씩 떨어뜨림*/
  width: 190px;
  height: 120px;
  left: 10px;
  top: 10px;
  transition: all .5s;
  font-size: 30px;
  color: #fff;
  font-weight: bold;
  display: flex;
  align-items: center;
  justify-content: center;
}

4 ) angle 구하기

💡 angle 공식

angle = 360 / carousel-card 개수
.carousel-card:nth-child(1) { transform: rotateY(  0deg); }
.carousel-card:nth-child(2) { transform: rotateY( 40deg); }
.carousel-card:nth-child(3) { transform: rotateY( 80deg); }
.carousel-card:nth-child(4) { transform: rotateY(120deg); }
.carousel-card:nth-child(5) { transform: rotateY(160deg); }
.carousel-card:nth-child(6) { transform: rotateY(200deg); }
.carousel-card:nth-child(7) { transform: rotateY(240deg); }
.carousel-card:nth-child(8) { transform: rotateY(280deg); }
.carousel-card:nth-child(9) { transform: rotateY(320deg); }

carousel-card를 r만큼을 떨어뜨려야 carousel-card끼리 겹치지 않고 제대로 보이게됩니다.

deg

💡 r를 구하는 방법 => 삼각비를 이용

  • r = (밑변의 길이) / tan(angle)
  • 여기서 구하려는 r = (card의 width / 2 ) / Math.tan(angle에 해당하는 radian)
    • radian = (angle / 2) * (Math.PI / 180)
      삼각비
      r 구하기

CSS로 설정

.carousel-card:nth-child(1) { transform: rotateY(  0deg) translateZ(288px); }
.carousel-card:nth-child(2) { transform: rotateY( 40deg) translateZ(288px); }
.carousel-card:nth-child(3) { transform: rotateY( 80deg) translateZ(288px); }
.carousel-card:nth-child(4) { transform: rotateY(120deg) translateZ(288px); }
.carousel-card:nth-child(5) { transform: rotateY(160deg) translateZ(288px); }
.carousel-card:nth-child(6) { transform: rotateY(200deg) translateZ(288px); }
.carousel-card:nth-child(7) { transform: rotateY(240deg) translateZ(288px); }
.carousel-card:nth-child(8) { transform: rotateY(280deg) translateZ(288px); }
.carousel-card:nth-child(9) { transform: rotateY(320deg) translateZ(288px); }

CSS 대신 javascript를 이용하여 carousel-card 배치를 동적으로 할당
동적으로 할당할 시, css를 일일이 추가할 필요가 없습니다.

const carouselCard = documnet.queryselector(".carousel-card");
const rataeAngle = 360 / carouselCard.length;
const radian = (ratateAngle / 2) * Math.PI / 180;
const tz = (210 / 2) / Math.tan(radin); 

carouselCard.forEach((el,idx)=>el.style.transform = `rotateY(${ratateAngle*idx}deg) translateZ(${tz}px)`);

casrousel의 Y축의 각도를 변경하여 carousel에 회전 효과를 줍니다.

casrousel angle = angle * 현재 카드의 index

왼쪽으로 회전 시키려면 Y축의 각도를 감소 시킵니다.
오른쪽으로 회전 시키려면 Y축의 각도를 증가 시킵니다.


html 코드

<button class="pre-btn">이전</button>
<button class="next-btn">다음</button>

<div class="scene">
  <div class="carousel">
    <div class="carousel-card">Card1</div>
    <div class="carousel-card">Card2</div>
    <div class="carousel-card">Card3</div>
    <div class="carousel-card">Card4</div>
    <div class="carousel-card">Card5</div>
    <div class="carousel-card">Card6</div>
    <div class="carousel-card">Card7</div>
    <div class="carousel-card">Card8</div>
    <div class="carousel-card">card9</div>
  </div>
</div>

CSS 코드

button{
  font-size: 20px;
  background-color: aliceblue;
  border: none;
  border-radius: 10px;
  padding: 10px 20px;
}
.scene {
  width: 210px;
  height: 140px;
  position: relative;
  /* 원근감을 위해 */
  perspective: 1200px;
  perspective-origin: center -60% ;
  margin: 0 auto;
  margin-top: 100px;
}
.carousel {
  width: 100%;
  height: 100%;
  position: absolute;
  /* perspective가 적용된 자식 요소들에 3D 효과 원근감*/
  transform-style: preserve-3d; 
  transition: all .5s;
}

.carousel-card {
  position: absolute;
  /* 셀 배치를 약간씩 떨어뜨림*/
  width: 190px;
  height: 120px ;
  transition: all .5s;
  font-size: 30px;
  color: #fff;
  font-weight: bold;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0.9;
}
.carousel-card:nth-child(1) {
  background-color: red;
}
.carousel-card:nth-child(2) {
 background-color: orange;
}
.carousel-card:nth-child(3) {
  background-color: yellow;
}
.carousel-card:nth-child(4) {
  background-color: green;
}
.carousel-card:nth-child(5) {
  background-color: blue;
}
.carousel-card:nth-child(6) {
  background-color: navy;
}
.carousel-card:nth-child(7) {
  background-color: purple;
}
.carousel-card:nth-child(8) {
  background-color: pink;
}
.carousel-card:nth-child(9) {
  background-color: salmon;
}

JS 코드

const preBtn = document.querySelector(".pre-btn");
const nextBtn = document.querySelector(".next-btn");
const carousel = document.querySelector(".carousel");
const carouselCard = document.querySelectorAll(".carousel-card");
const scene = document.querySelector(".scene");

let angle = 0;
let index = 0;

// 회전각도 구하기
const rotateAngle = 360 / carouselCard.length;

// Math.tan를 사용 => 각도를 라디안 값으로 변환
const radian = ((rotateAngle / 2) * Math.PI) / 180;

//원의 중심점에서 떨어진 거리 구하기 (밑변의 길이 / tan(각도에 해당하는 라디안))
const colTz = Math.round(210 / 2 / Math.tan(radian));

// 초기 셀 각도 및 중심점에서 떨어진 거리 세팅
carouselCard.forEach(
  (el, idx) =>
    (el.style.transform = `rotateY(${rotateAngle * idx}deg) translateZ(${colTz}px)`),
);

// 왼쪽으로 회전
preBtn.addEventListener("click", () => {
  console.log(angle);
  angle -= rotateAngle;
  carousel.style.transform = `rotateY(${-angle}deg)`;
});

 // 오른쪽으로 회전
nextBtn.addEventListener("click", () => {
  console.log(angle);
  angle += rotateAngle;
  carousel.style.transform = `rotateY(${-angle}deg)`;
});

추가 ) x축, y축를 축 전환시켜 전환 효과 주기

1 ) carousel, carousel-card 들의 translate 회전 축 변경

  • X축으로 회전 시 : transform = translateY() => transform = translateX()로 변경
  • Y축으로 회전 시 : transform = translateX() => transform = translateY()로 변경

2 ) r 길이 변경 [ r = (card-width or card-height / 2) / Math.tan(Radin) ]

  • X축으로 회전 시 : card-width 값 대신 card-height 값 대입
  • Y축으로 회전 시 : card-height 값 대신 card-width 값 대입

3 ) classList row 동적으로 추가/제거 하여 동적으로 회전 축 방향과 r길이를 변경 시킵니다.

  • 축을 변경시 classList row 유무를 통해 transform 회전 축을 동적으로 변경시킵니다.
  • r길이 또한 classList row 유무를 통해 동적으로 변경시킵니다.

최종 코드

전체 구현 코드는 Codepen를 참고 해주세요.


참고 사이트

https://3dtransforms.desandro.com/carousel

profile
함께 개선하는 개발자

0개의 댓글

관련 채용 정보