일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- Project
- html
- Typescript
- todolist
- React
- Timer
- Calculator
- 웹사이트
- darknode
- localStorage
- mini_project
- DOM
- javascript
- stopwatch
- CSS
- 브라우저
- calender
- Today
- Total
이야기 정리
타입스크립트 - 무한슬라이드(캐러셀) 만들기 본문
개발 동기
이전에 '오늘의 제주' 사이트를 개발할 때 오토캐러셀은 편의상 라이브러리를 활용했다. 그러나 라이브러리만 쓰고 넘어가는 것보다 라이브러리에 있던 기능을 내가 아는 오토캐러셀에 추가해보는 것이 좋다고 생각해 만들게 됐다.
또, '오늘의 제주' 때에는 타입스크립트를 활용하지 않았기에 이번에는 타입스크립트를 사용해 타입을 명확히하며 오토캐러셀이 정확히 어떤 원리로 돌아가는지 살피며 코드를 만들었다.
필요기능
- 3초마다 자동으로 넘어가기
- 버튼 클릭 시 이전, 다음으로 넘어가기
- 하단 도트버튼 클릭 시 해당 번호의 슬라이드로 넘어가기
- 슬라이드에 마우스를 올리는 동안 슬라이드는 정지해야한다. -> 사용자가 내용을 읽기 편하게 하기 위해
완성코드
See the Pen Carousel by beren-105 (@beren-105) on CodePen.
전체 코드 보기 : https://github.com/beren-105/Mini-Project/tree/main/9_Carousel
1. 변수 선언 및 클론 만들기
무한 슬라이드는 앞과 뒤 쪽으로 클론을 만들고, 마지막에 도달하면 transition을 none으로 설정해 처음 위치로 돌려준다. 그리고 다시 transition을 설정하는 식의 눈속임으로 만들어준다.
그래서 먼저 즉시실행 함수를 이용해 클론을 만들었다.
발생한 문제 : cloneNode의 타입지정
cloneNode를 만드니 appendChild를 할 때 Node 타입이라 안된다는 에러가 발생했다.
이럴 땐 당황하지 말고 타입을 강제로 교체하자.
as를 사용해 타입을 본래의 HTMLDivElement로 강제로 변환했다. as를 남용하는 것은 좋은 습관은 아니나, 검색해 봐도 별다른 해결법이 없었다.
// 필요한 변수선언
const prevBtn = document.querySelector('.prev-btn');
const nextBtn = document.querySelector('.next-btn');
const carousel = document.querySelector('.carousel');
const slide = document.querySelector('.imgs');
const imgs = document.querySelectorAll('.img');
// clientWidth : 패딩값과 너비값을 포함한 값으로, 마진값을 나타나지 않는다.
const size = imgs[0].clientWidth;
const startPosition = size*imgs.length;
let index = 0;
let autoSlide :number;
(function cloneSlide() {
// 타입 어설션(type assertion)를 통한 강제 타입 교체
// cloneNode : 복제된 node를 반환한다. 사용법 -> 기존노드.cloneNode(복제할노드)
if (slide instanceof HTMLDivElement) {
for (let i = 0; i < imgs.length; i ++) {
const imgClone = imgs[i].cloneNode(true) as HTMLDivElement;
imgClone.setAttribute('id', 'clone')
slide.appendChild(imgClone);
}
for (let i = imgs.length - 1; i >= 0 ; i --) {
const imgClone = imgs[i].cloneNode(true) as HTMLDivElement;
imgClone.setAttribute('id', 'clone')
slide.prepend(imgClone);
}
slide.style.transform = `translateX(-${startPosition}px)`;
}
})();
2. 이벤트 생성 및 슬라이드 이동
이번에 만들 기능은 3초마다 자동으로 슬라이드가 넘어가는 기능과, 버튼 클릭 시 이전, 이후로 넘어가는 기능이다.
먼저 필요한 이벤트들을 모두 선언했다. 이 중 몇몇 기능은 당장 사용하지 않으니 넘어가도 괜찮다.
// 하단 도트 버튼 만들기
document.addEventListener('DOMContentLoaded', createBtn);
// 자동으로 넘어가게 하기
autoSlide = setInterval(nextSlide, 2000);
if (slide instanceof HTMLDivElement
&& carousel instanceof HTMLDivElement
&& prevBtn instanceof HTMLButtonElement
&& nextBtn instanceof HTMLButtonElement
) {
// 버튼 클릭 이벤트
prevBtn.addEventListener('click', prevSlide);
nextBtn.addEventListener('click', nextSlide);
// 무한으로 이어지는 것처럼 보이게 하기
slide.addEventListener('transitionend', resetSlide);
// 마우스오버 이벤트
carousel.addEventListener('mouseover', handleMouseOver);
carousel.addEventListener('mouseleave', handleMouseLeave);
}
선언을 끝냈으면 코드를 짤 시간이다. 나는 다음으로 넘어가는 버튼과 자동으로 넘어가는 함수를 nextSlide라는 함수 하나로 통일 시켰다.
// 다음으로 넘어갈 때
function nextSlide() {
if (slide instanceof HTMLDivElement && index < imgs.length) {
index++;
slide.style.transition = '0.4s';
slide.style.transform = `translateX(-${index * size + startPosition}px)`;
}
}
// 이전으로 넘어갈 때
function prevSlide() {
if (slide instanceof HTMLDivElement && index >= 0) {
index--;
slide.style.transition = '0.4s';
slide.style.transform = `translateX(-${index * size + startPosition}px)`;
}
}
슬라이드가 끝나 무한으로 이어지는 것처럼 보이는 부분은 transitionend이벤트를 사용한다. transitionend 이벤트는 이미지가 넘어가고 난 뒤 실행되는 이벤트다.
문제는 이미지가 다 넘어가기도 전에 사용자가 버튼을 마구잡이로 누르면 transitionend가 제대로 발생하지 않을 수 있다!
그렇기 때문에 이전/다음으로 넘어가는 이벤트에 index가 이미지의 개수보다 커지지 않게 if문을 작성해 막아줘야한다.
// 무한으로 이어지는 것처럼 보이게 하게
function resetSlide() {
if (slide instanceof HTMLDivElement) {
if (index === imgs.length) {
slide.style.transition = 'none';
index = 0;
slide.style.transform = `translateX(-${index * size + startPosition}px)`
}
if (index < 0) {
slide.style.transition = 'none';
index = imgs.length-1;
slide.style.transform = `translateX(-${index * size + startPosition}px)`
}
}
dotBtnActive()
}
가장 마지막에 적힌 dotBtnActive 함수는 하단 도트버튼 이벤트로, 가장 마지막에 다룰 예정이다.
3. 마우스 이벤트 : 슬라이드 마우스오버 시 슬라이드 멈추기
슬라이드를 멈추는건 제일 간단하다.
먼저, 변수로 setInterval 함수를 선언한다.
마우스가 올라갔을 때 clearInterval로 선언한 변수의 이벤트를 없앤다.
그리고 마우스가 떠났을 때 다시 변수를 선언한다.
function handleMouseOver(e :MouseEvent) {
if (e.currentTarget) clearInterval(autoSlide);
}
function handleMouseLeave(e :MouseEvent) {
if (e.currentTarget) autoSlide = setInterval(nextSlide, 2000);
}
4. 하단 도트버튼
도트버튼은 이미지의 개수에 따라 자동으로 추가되거나, 제거 될 수 있도록 HTML이 아닌 타입스크립트에서 만들었다.
DOMContentLoaded 이벤트로 처음에 버튼이 생성되게 만들었다. data-id를 설정한 이유는 index와 데이터가 일치하면 활성화되게 만들기 위해서다.
function createBtn() {
const slideBtns = document.querySelector('.slide-btns');
for (let i = 0; i < imgs.length; i ++) {
const btn = document.createElement('button');
btn.classList.add('slide-btn');
btn.setAttribute('data-id', `${i}`)
if (slideBtns instanceof HTMLDivElement) {
slideBtns.appendChild(btn);
}
btn.addEventListener('click', handleDotBtn);
dotBtnActive()
}
}
다음은 도트 버튼을 클릭했을 때 해당 이미지로 이동하는 함수다.
첫번째 도트 버튼을 클릭 시 첫번째로, 네번째를 클릭 시 네번째로 이동한다.
먼저 처음에 모든 버튼에서 active 클래스를 없애주고, 클릭한 버튼과 일치할 시에만 active 클래스를 생성했다.
처음 클론을 생성했을 때처럼 FocusEvent에는 classList를 추가할 수 없어서 강제로 타입을 교체하는 과정을 거쳤다.
function handleDotBtn({ target }:FocusEvent) {
const slideBtns = document.querySelectorAll('.slide-btn')
slideBtns.forEach((btn) => {
if (btn instanceof HTMLButtonElement) {
btn.classList.remove('active')
if (slide instanceof HTMLDivElement && target) {
const targetBtn = target as HTMLButtonElement;
index = Number(targetBtn.dataset.id);
targetBtn.classList.add('active');
slide.style.transition = '0.4s';
slide.style.transform = `translateX(-${index * size + startPosition}px)`;
}
}
})
}
마지막으로 슬라이드가 자동으로 넘어가거나, 이전/다음 버튼을 클릭 했을 때 도트버튼도 같이 활성화가 되게 만들면 끝!
위 코드와 유사하지만, index와 같은 data-id의 버튼만 찾는다는 점, 슬라이드를 도트 버튼에서 이동시키지 않는다는 점이 다르다.
function dotBtnActive() {
const slideBtns = document.querySelectorAll('.slide-btn')
slideBtns.forEach((btn) => {
if (btn instanceof HTMLButtonElement) {
btn.classList.remove('active')
if (slide instanceof HTMLDivElement
&& Number(btn.dataset.id) === index
) {
index = Number(btn.dataset.id);
btn.classList.add('active');
}
}
})
}
여기까지가 무한 슬라이드의 완성 과정이다.
처음 노드를 복사하는 과정에서 하나의 이미지만 복사한 탓에 무한 슬라이드가 제대로 동작하지 않았다. 하지만 아예 모든 이미지를 앞뒤로 복사해 여러 이미지가 함께 보일 수 있도록 바꾸었다. 단순한 자동 캐러셀에 다양한 기능을 추가할 수 있어서 즐거운 작업이었다.
'Project > TypeScript' 카테고리의 다른 글
타입스크립트 - 피아노 만들기 (0) | 2023.03.13 |
---|---|
타입스크립트 - 캘린더 만들기 (0) | 2023.02.15 |