메인 페이지 배너를 만들기 위해 앞, 뒤 화살표 버튼을 누르면 이미지가 변경되는 캐러셀(Carousel) 컴포넌트를 만들었다.
<transition> 써보려고 했는데 정확히 이해하지 못해서 그냥 없이 했다.😇
*프리픽 이미지를 사용했다.
영역의 너비를 고정하고 안에 들어갈 이미지들의 위치를 조정하는 방식으로 만든다. 밑에는 '1/3'과 같이 표시되는 페이지네이션을 넣었다.
필요한 객체는 다음과 같다.
- state: 이미지들과 슬라이드에 현재 보여지는 이미지 정보. 업데이트를 트리거할 수 있도록 reactive로 생성한다.
- items: 이미지 이름이 들어있는 배열
- currentIndex: items 중 현재 보여지는 이미지의 인덱스값
- prevSlide: 클릭하면 currentIndex--를 하여 앞 이미지를 보여준다.
- nextSlide: 클릭하면 currentIndex++를 하여 뒷 이미지를 보여준다.
- slideTranslateX: currentIndex에 따라 각 이미지의 위치를 변경해준다.
하면서 막혔던 부분이 몇 가지 있었는데 console.log로 출력하면서 고쳤다.
이미지 위치를 "transform: translateX(값%)"으로 조정했는데, 값은 해당 이미지의 (index - state.currentIndex) * 100으로 계산하면 된다.
| state.currentIndex: 0 | |
| index | translateX(%) |
| 0 | 0 |
| 1 | 100 |
| 2 | 200 |
| state.currentIndex: 1 | |
| 0 | -100 |
| 1 | 0 |
| 2 | 100 |
| state.currentIndex: 2 | |
| 0 | -200 |
| 1 | -100 |
| 2 | 0 |
| => (state.currentIndex - index)*100 | |
currentIndex의 값을 변경할 때 인덱스 값으로 유지하기 위해 (state.currentIndex-1)%state.items.length로 계산했는데, prevSlide의 경우 음수를 나누면 값이 제대로 안 나오기 때문에 state.items.length를 한 번 더해서 양수로 유지해야 한다.
테스트를 위해 같은 이미지를 색만 바꿔서 "src/assets/img/"에 저장했다. 이미지의 너비는 넣고 싶은 영역에 딱 맞도록 만들었다. 이미지가 영역보다 작으면 여백이 남아서 이미지에 맞춘 배경색을 따로 넣어야 하기 때문이다. 이미지 이름은 banner01, banner02, banner03이다. 혹시 이름을 다르게 쓸 수도 있으니까 저렇게 했는데 아예 01, 02, 03으로 하는게 나으려나...
이미지 경로는 require로 입력해야 한다. require을 사용하지 않으면 src에 써있는 게 그대로 나온다. vue에서 경로 맨 앞에 '@'를 쓰면 src라는 뜻이다.
폴더에 있는 이미지를 모두 가져오는 다른 방법이 있다고 하는데 막상 해보니까 잘 안 되어서 일단 저런 방식으로 진행했다.
결과적으로 다음과 같이 작성했다.
*chevron_left와 chevron_right는 material symbols 아이콘을 사용했다.
<template><div class="slide-section"><div class="slides"><div v-for="(item, index) in state.items" :key="index" class="slide-item" :style="{ transform: `translateX(${slideTranslateX(index)}%)`,}"><div>{{item}}</div></div></div><div class="slide-button-container"><button @click="prevSlide" class="slide-nav-button prev-button"><span class="material-symbols-rounded">chevron_left</span></button><button @click="nextSlide" class="slide-nav-button next-button"><span class="material-symbols-rounded">chevron_right</span></button></div><div class="slide-pagenation-container"><button btntype="slideNav" class="slide-pagenation"><span>{{ state.currentIndex + 1 }}</span>/<span>{{ state.items.length }}</span></button></div></div></template><script setup>import { reactive } from 'vue';const state = reactive({currentIndex: 0,items: ['banner01', 'banner02', 'banner03'],});const prevSlide = () => {state.currentIndex =(state.currentIndex + state.items.length - 1) % state.items.length;};const nextSlide = () => {state.currentIndex = (state.currentIndex + 1) % state.items.length;};const slideTranslateX = (index) => {return (index - state.currentIndex) * 100;};</script>
버튼은 양 끝에 고정되게 했고, 페이지네이션은 오른쪽 아래에 고정되게 했다.
버튼 위치가 이미지의 중요한 부분에 맞춰서 최대 1200px에 맞춰지도록 했다. 애초에 이미지를 만들 때 꽉 채우지 않고 양 옆에 여백을 두었다.
이렇게 보니까 버튼 배경색이 너무 투명한가 싶다.
너비가 많이 줄었을 때 이미지가 지나치게 잘리는데 이건 나중에 생각해봐야 할 것 같다.
<style scoped>.slide-section {height: 395px;width: 100%;position: relative;overflow: hidden;display: flex;justify-content: center;align-items: center;}.slides {margin-inline: auto;display: flex;}.slide-button-container,.slide-pagenation-container {position: absolute;width: calc(100% - 40px);max-width: 1200px;display: flex;justify-content: space-between;margin-inline: 20px;}/*이미지 스타일*/.slide-item {transition: transform 0.3s ease-in-out;box-sizing: border-box;display: block;overflow: hidden;position: absolute;inset: 0px;}.slides img {position: absolute;inset: 0px;box-sizing: border-box;margin: auto;display: block;width: 0px;height: 0px;min-width: 100%;max-width: 100%;min-height: 100%;max-height: 100%;object-fit: cover;}.slide-pagenation {position: absolute;font-size: 12px;font-weight: 500;padding: 4px 10px;border-radius: 12px;width: 36px;gap: 4px;bottom: 28px;right: 0;margin-right: 72px;cursor: inherit;}/*페이지네이션 스타일*/.slide-pagenation-container {bottom: 0;}.slide-button-container .slide-nav-button {border-radius: 50%;}.slide-pagenation span {width: 16px;}/*원래 다른 버튼 Vue 파일에 있던 CSS 스타일*/button {padding: 10px;border-radius: 4px;font-weight: 700;font-size: 18px;cursor: pointer;display: flex;justify-content: center;background-color: rgba(25, 28, 26, 0.2);color: white;border: none;}</style>
CSS 수정하다가 잘 안 되는 부분은 마켓컬리 메인 페이지 배너를 참고했다.
튜토리얼 따라할 때는 잘 할 수 있을까 싶었는데 이제 v-for 쓰는 것도 익숙해진 것 같다.


0 댓글