'회전목마'라는 뜻으로 슬라이드쇼와 같은 방식으로 콘텐츠를 표시하는 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>
<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;
}
.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;
}
💡 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끼리 겹치지 않고 제대로 보이게됩니다.
💡 r를 구하는 방법 => 삼각비를 이용
- r = (밑변의 길이) / tan(angle)
- 여기서 구하려는 r = (card의 width / 2 ) / Math.tan(angle에 해당하는 radian)
- radian = (angle / 2) * (Math.PI / 180)
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)`;
});
1 ) carousel, carousel-card 들의 translate 회전 축 변경
transform = translateY()
=> transform = translateX()
로 변경transform = translateX()
=> transform = translateY()
로 변경2 ) r 길이 변경 [ r = (card-width or card-height
/ 2) / Math.tan(Radin) ]
card-width
값 대신 card-height
값 대입card-height
값 대신 card-width
값 대입3 ) classList row 동적으로 추가/제거 하여 동적으로 회전 축 방향과 r길이를 변경 시킵니다.
전체 구현 코드는 Codepen를 참고 해주세요.