
pagination의bullets요소를 css 커스텀 하는것이 아닌, 특이한 모양의 아예 다른 버튼을pagination으로 만들고 싶어svg를 사용했다.
하지만 많은 난관이 있었는데, 기존 내장된pagination을 쓴다면 스타일만 바꾸면 되지만 아예 다른 요소를 버튼으로 사용해야했기 때문에 따로 스크립트를 추가해주어야 했다.
- prev, next 버튼 작동 & pagination 작동

우선 svg 파일을 생성해볼 것이다. 일러스트에서 각기 다른 버튼들을 뭉탱이로 벡터화하여 아트보드 안에 담고, 이를 svg 로 확장하여 다운로드해준다.


해당 파일을 크롬창 상단바에 끌어당기면 이렇게 svg 가 어떻게 생성되었는지 태그까지 알 수 있다.

svg 는 태그상에서 배경색, 요소의 색 등을 커스텀할 수 있는 장점이 있는데, 다만 가독성이 너무 안좋아 <g> 태그를 사용하여 class 와 id 를 주어 공통된 요소끼리 path 를 그룹화 해보았다.
⇒ sec-btn 요소들을 클릭하면 스와이퍼의 슬라이더가 그에 따라 작동되게끔 구현을 목표

<div class="example pc-only">
<!-- svg 태그 -->
<svg xmlns="http://www.w3.org/2000/svg"... </svg>
<!-- swiper -->
<div class="example-wrap">
<div class="swiper container2">
<div class="swiper-wrapper">
<div class="swiper-slide">page 1</div>
<div class="swiper-slide">page 2</div>
<div class="swiper-slide">page 3</div>
<div class="swiper-slide">page 4</div>
<div class="swiper-slide">page 5</div>
<div class="swiper-slide">page 6</div>
</div>
<div class="circle-btn">
<button class="btn-prev">이전</button>
<button class="btn-next">다음</button>
</div>
<div class="swiper-pagination"></div>
</div>
</div>
</div>
// 스와이퍼 기본 스크립트
const mySwiper = new Swiper('.container2', {
effect: 'fade',
loop: true,
slidesPerView: 1,
navigation: {
nextEl: '.circle-btn .btn-next',
prevEl: '.circle-btn .btn-prev',
},
pagination: {
el: '.swiper-pagination',
clickable: true,
},
});
처음엔 Swiper 의 클래스(swiper-slide-active)만 이용하면 조작할 수 있을것이라 생각했지만, 해당 클래스 뿐만아니라 매번 바뀌는 swiper-slide-prev 와 swiper-slide-next 라는 클래스, 그리고 계속 바뀌는 slider 들의 태그상 순서때문에 굉장히 노가다로 코드를 작성했다.
하지만 굉장히 바보같은 행위였고, Swiper 의 이벤트 객체나 내장함수를 활용하면 훨씬 간편하게 기능을 커스텀 할 수 있다는 것을 알게되어 몇 시간에 걸쳐진 코드를 무참히 다 지웠다..
// 추가 스크립트
// 스와이퍼 페이지 이동 시 해당 버튼 active 적용
const $$circleBtn = document.querySelectorAll('.example .sec-btn');
const $$swiperBullets = document.querySelectorAll('.example .swiper-pagination-bullet');
// 슬라이드 페이지에 따른 버튼 활성화(mySwiper.activeIndex 작동 오류로 pagination 사용)
mySwiper.on('slideChange', function (e) {
// console.log('슬라이드 ----')
const mySwiperPagination = e.pagination.bullets;
let activeIndex = 0;
mySwiperPagination.forEach((bullet, bulletIndex) => {
if (bullet.classList.contains('swiper-pagination-bullet-active')) {
activeIndex = bulletIndex;
}
});
$$circleBtn.forEach((btn, index) => {
btn.classList.toggle('active', index === activeIndex);
});
});
// svg 버튼 클릭 시 페이지 활성화 (slideTo() 작동 오류로 pagination bullet 버튼 강제 클릭 유도)
$$circleBtn.forEach((btn, index) => {
btn.addEventListener('click', () => {
// console.log('버튼 클릭 ----')
let needToClick;
$$swiperBullets.forEach((bullet, bi) => {
if (bi === index) {
needToClick = bullet;
}
});
if (needToClick) {
needToClick.click(); // 클릭 이벤트 실행
} else {
console.log('해당 인덱스를 가진 요소를 찾을 수 없습니다.');
}
});
});
활성화된 슬라이드의 index 값에 따라 해당하는 index 의 svg 버튼 또한 active 클래스를 주어 활성화시키려했다. 여기서 Swiper 의 이벤트 객체 중 activeIndex 라는게 있었는데, 현제 활성화된 슬라이더의 index 값을 반환해주는 것이었다.
mySwiper.on('slideChange', function (e) { 에서 console.log(e) 로 로그를 찍어보면 다음과 같이 나온다.

하지만 문제가 있었다. 스와이퍼에 loop 옵션을 준 상태라 끝에서 다음 버튼을 누르면 다시 처음으로 돌아가게끔 했는데, 마지막 index(5) 에 있는 상태에서 next 버튼을 눌러도 activeIndex 가 0이 되지 않고 계속 마지막 index 값을 유지하는 것이었다ㅠ 그에 대한 차선책으로 다른 객체 요소인 pagination 을 이용하게 되었다. pagination 의 bullets 배열요소들이 있고, 그 안에서 현재 활성화된 요소의 index 를 알 수 있었다.

const mySwiperPagination = e.pagination.bullets;
let activeIndex = 0;
mySwiperPagination.forEach((bullet, bulletIndex) => {
if (bullet.classList.contains('swiper-pagination-bullet-active')) {
activeIndex = bulletIndex;
}
});
$$circleBtn.forEach((btn, index) => {
btn.classList.toggle('active', index === activeIndex);
});
그 원인을 알게되었다. Swiper 에서 loop 가 true 인 상태에서는 realIndex 을, false 인 상태에서는 activeIndex 를 사용하는 것이었다..! 위의 길고 복잡한 코드가 아래와 같이 간단해졌다 🥹
const activeIndex = e.realIndex;
$$circleBtn.forEach((btn, index) => {
btn.classList.toggle('active', index === activeIndex);
});
참고: https://uiweb.tistory.com/46
이후 mySwiper 객체의 on 속성으로 해당 스크립트를 넣었다. (on: { slideChange: function (e) {})
반대로 svg 버튼을 클릭할 시 해당하는 슬라이드가 활성화되도록 해보았다. 그 중 slideTo() 라는 함수가 있었는데, ()안에 index 값(0부터 시작)을 입력하면 해당 index 로 슬라이드가 활성화되는 것이었다.
svg 버튼에 addEventListener 로 클릭이벤트를 주어 클릭한 index 값으로 slideTo() 를 실행시켰다.
$$circleBtn.forEach((btn, index) => {
btn.addEventListener('click', () => {
mySwiper.slideTo(index)
console.log('index: ', index)
});
});

버튼을 클릭해도 클릭한 버튼의 index 와 다른 엉뚱한 버튼과 슬라이드가 활성화되어 콘솔을 확인해보니 index 값과 realIndex 의 값이 다르게 나오는 것을 확인할 수 있다ㅠ 원인을 알고싶었지만 찾을 수 없었고 결국 다른 방법을 생각했다.
pagination 버튼들은 해당 슬라이드들이 활성화되는 등 잘 작동하기 때문에, svg 버튼을 클릭하면 그와 똑같은 index 값을 가진 pagination 의 bullet 이 강제로 클릭되게끔 스크립트를 구현했다.
(pagination 은 css 로 hidden 처리)

$$circleBtn.forEach((btn, index) => {
btn.addEventListener('click', () => {
let needToClick;
$$swiperBullets.forEach((bullet, bi) => {
if (bi === index) {
needToClick = bullet;
}
});
if (needToClick) {
needToClick.click(); // 클릭 이벤트 실행
} else {
console.log('해당 인덱스를 가진 요소를 찾을 수 없습니다.');
}
});
});
위와 같이 구현했더니 버튼, 슬라이드 모두 정상적으로 기능을 하였다.
원인을 찾았다.! loop 일 땐 slideToLoop() 를 사용해야했다.
https://stackoverflow.com/questions/60401044/swiper-slider-slideto-method-with-loop
const mySwiper = new Swiper('.container2', {
effect: 'fade',
loop: true,
slidesPerView: 1,
navigation: {
nextEl: '.circle-btn .btn-next',
prevEl: '.circle-btn .btn-prev',
},
pagination: {
el: '.swiper-pagination',
clickable: true,
},
on: { // 슬라이드에 따른 svg 버튼 활성화
slideChange: function (e) {
const activeIndex = e.realIndex;
$$circleBtn.forEach((btn, index) => {
btn.classList.toggle('active', index === activeIndex);
});
}
}
});
// 스와이퍼 페이지 이동 시 해당 버튼 active 적용
const $$circleBtn = document.querySelectorAll('.example .sec-btn');
// svg 버튼 클릭 시 페이지 활성화
$$circleBtn.forEach((btn, index) => {
btn.addEventListener('click', () => {
mySwiper.slideToLoop(index)
});
});
반응형 스와이퍼를 만들기 위해 다음과 같이 코드를 작성한 상황이다.
mySwiper, mySwiper2 이렇게 두 개의 Swiper 객체로 따로 적용window.addEventListener('resize', init);pc, 모바일 버전으로 화면 크기를 바꿀 때 바껴진 후 swiper 가 제대로 작동하지 않는 문제가 생겼다. 이는 금방 해결되었는데, 스와이퍼에 적용되는 element 가 바뀔 때마다 swiper.update() 를 실행하여 swiper 를 갱신해주는 것이었다.
참고:
https://ducklett.tistory.com/3
https://velog.io/@1000peach/Error-swiper-content-update-시-정상적으로-스와이프-되지-않는-현상-bwx2j0jm
mySwiper.update()
resize 콜백 함수 안에서 버튼 element 가 할당될 때 스와이퍼도 같이 갱신해주었다.
let $$circleBtn;
const swiperOptions = {
effect: 'fade',
loop: true,
slidesPerView: 1,
navigation: {
nextEl: '.circle-btn .btn-next',
prevEl: '.circle-btn .btn-prev',
},
pagination: {
el: '.swiper-pagination',
clickable: true,
},
on: {
slideChange: function (e) {
const activeIndex = e.realIndex;
$$circleBtn?.forEach((btn, index) => {
btn.classList.toggle('active', index === activeIndex);
});
}
}
}
// pc
const mySwiper2 = new Swiper('.container2', swiperOptions);
// mo
const mySwiper = new Swiper('.container', swiperOptions);
function initializeVariables() {
if (isMobile()) {
$$circleBtn = document.querySelectorAll('.example .sec-btn');
mySwiper.update()
clickBtn($$circleBtn, mySwiper);
} else {
$$circleBtn = document.querySelectorAll('.example2 .sec-btn');
mySwiper2.update()
clickBtn($$circleBtn, mySwiper2);
}
}
document.addEventListener('DOMContentLoaded', initializeVariables);
window.addEventListener('resize', initializeVariables);
// svg 버튼 클릭 시 페이지 활성화
function clickBtn(btns, swiper) {
btns.forEach((btn, index) => {
btn.addEventListener('click', () => {
swiper.slideToLoop(index);
});
});
}