오늘은 예전에 만들었던 캐러셀 UI를 소환해서
여기에 터치 & 스와이프 기능을 만들어보자.
예전에 만든 캐러쉘 UI
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
crossorigin="anonymous" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="./slide.css" />
<title>Document</title>
</head>
<body>
<div style="overflow: hidden">
<div class="slide-container">
<div class="slide-box">
<img src="./assets/car1.png" />
</div>
<div class="slide-box">
<img src="./assets/car2.png" />
</div>
<div class="slide-box">
<img src="./assets/car3.png" />
</div>
</div>
</div>
<button class="btn1">1</button>
<button class="btn2">2</button>
<button class="btn3">3</button>
<button class="prev"><</button>
<button class="next">></button>
<script src="./slide.js"></script>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
crossorigin="anonymous"></script>
</body>
</html>
const slideContainer = document.querySelector(".slide-container");
const carouselBtn1 = document.querySelector(".btn1");
const carouselBtn2 = document.querySelector(".btn2");
const carouselBtn3 = document.querySelector(".btn3");
const carouselNext = document.querySelector(".next");
const carouselPrev = document.querySelector(".prev");
carouselBtn1.addEventListener("click", handleCarousel1);
carouselBtn2.addEventListener("click", handleCarousel2);
carouselBtn3.addEventListener("click", handleCarousel3);
carouselNext.addEventListener("click", handleCarouselNext);
carouselPrev.addEventListener("click", handleCarouselPrev);
//캐러쉘 1번
let nowPhoto = 0;
function handleCarousel1() {
nowPhoto = 0;
slideContainer.style.transform = `translateX(-${nowPhoto}00vw)`;
}
//캐러쉘 2번
function handleCarousel2() {
nowPhoto = 1;
slideContainer.style.transform = `translateX(-${nowPhoto}00vw)`;
}
//캐러쉘 3번
function handleCarousel3() {
nowPhoto = 2;
slideContainer.style.transform = `translateX(-${nowPhoto}00vw)`;
}
//캐러쉘 Next버튼
function handleCarouselNext() {
if (nowPhoto == 0) {
nowPhoto += 1;
slideContainer.style.transform = `translateX(-${nowPhoto}00vw)`;
} else if (nowPhoto == 1) {
nowPhoto += 1;
slideContainer.style.transform = `translateX(-${nowPhoto}00vw)`;
}
}
//캐러쉘 Prev버튼
function handleCarouselPrev() {
if (nowPhoto == 2) {
nowPhoto -= 1;
slideContainer.style.transform = `translateX(-${nowPhoto}00vw)`;
} else if (nowPhoto == 1) {
nowPhoto -= 1;
slideContainer.style.transform = `translateX(-${nowPhoto}00vw)`;
}
}
.slide-container {
width: 300vw;
transition: all 1s;
}
.slide-box {
width: 100vw;
float: left;
}
.slide-box img {
width: 100%;
}

이전에 만든 캐러쉘 코드이다.
터치되는 캐러셀같은거 조작해보면 대충 이런 기능이 들어있다.
기능1. 드래그한 거리만큼 사진도 왼쪽으로 움직여야함
기능2. 마우스 떼었을 때 일정거리 이상 이동했으면 사진2 보여줌, 아니면 다시 사진1 보여줌
기능1 부터 만들어보자.
근데 이거 만들려면 알아야할 이벤트가 3개 있다.
마우스로 어떤 HTML 요소를 조작할 때 발동하는 이벤트가 있습니다.
mousedown (어떤 요소에 마우스버튼 눌렀을 때)
mouseup (어떤 요소에 마우스버튼 뗐을 때)
mousemove (어떤 요소위에서 마우스 이동할 때)
slideContainer.addEventListener("mousemove", function () {
console.log("하이");
});
예를 들어 이렇게 코드짜면 .slide-box 위에 마우스 움직일 때 마다 '안녕'이 출력된다.
slideContainer.addEventListener("mousemove", function (e) {
console.log(e.clientX);
});
이게 더 유용한데 mouse이벤트 이벤트리스너안에선 e.clientX, e.clientY를 출력해볼 수 있는데
현재 마우스 좌표를 알려준다.
이걸 쓰면 유저가 얼마나 사진을 드래그 했는지 그런 것도 알 수 있을 것이다!
예를 들어 사진1을 클릭하고 왼쪽으로 50px 잡아끌었다면
사진1도 왼쪽으로 50px 움직여야한다.
근데 사진1만 움직이는거말고 사진3개 전부 담긴 큰 박스가 움직이는게 좋을 것 같다.
이동거리를 어떻게 파악할까?
마우스 누를 때의 X좌표 & 마우스 움직일 때의 X좌표를 빼보면 나온다!
slideContainer.addEventListener("mousedown", function (e) {
console.log(e.clientX);
});
slideContainer.addEventListener("mousemove", function (e) {
console.log(e.clientX);
});
저 위의 e.clientX 두개를 빼면 된다.
근데 안타깝게도 모든 변수는 범위가 있어서 함수 바깥으로 탈출할 수 없다.
하지만 함수 바깥에 있던 변수는 함수 안에서 맘대로 쓸 수 있다.
그래서 함수 바깥에 변수를 만들어두면 함수들끼리 변수 공유가 가능하다.
let startCoordinate = 0;
slideContainer.addEventListener("mousedown", function (e) {
startCoordinate = e.clientX;
});
slideContainer.addEventListener("mousemove", function (e) {
console.log(e.clientX - startCoordinate);
});
let startCoordinate 저장해둔다.mousemove 이벤트발생시 let startCoordinate랑 현재좌표인 e.clientX를 빼준다.참고로 저렇게 모든지역에서 이용할 수 있는 변수들은 전역변수라고 한다.
전역 변수 vs 지역 변수
let startCoordinate = 0;
slideBox.addEventListener("mousedown", function (e) {
startCoordinate = e.clientX;
});
slideBox.addEventListener("mousemove", function (e) {
slideContainer.style.transform = `translateX(${
e.clientX - startCoordinate
}px)`;
});
결과를 보면 마우스 클릭도 안했는지 혼자서 움직이고 있다.

"클릭하고나서만 박스 이동해달라"고 컴퓨터에게 명령을 주지 않았기 때문이다.
slideBox.addEventListener("mousemove", function (e) {
if (마우스를 누르면) {
slideContainer.style.transform = `translateX(${
e.clientX - startCoordinate
}px)`;
}
});
마우스를 눌렀는지 판단하는 것은 mousedown이벤트를 통해 알 수 있다.
코드를 짜보자
let startCoordinate = 0;
let pressDown = false;
let pressNot = false
slideBox.addEventListener("mousedown", function (e) {
startCoordinate = e.clientX;
pressDown = true;
});
slideBox.addEventListener("mousemove", function (e) {
if (pressDown == true) {
slideContainer.style.transform = `translateX(${
e.clientX - startCoordinate
}px)`;
}
});
전역변수를 통해 눌렀는지 안눌렀는지 판단하는 기능을 만들었다.

결과를 보면 아직도 무언가 이상하다고 느껴질 수 있다.
"마우스 떼었을 때는 박스 움직이지 말라"고 컴퓨터에게 명령을 내지 않았기 때문이다.
여기서 사용해야할 이벤트는 mouseup기능이다.
slideBox.addEventListener("mouseup", function (e) {
pressDown = false;
if (e.clientX - startCoordinate < -100) {
slideContainer.style.transform = `translateX(-100vw)`;
} else {
slideContainer.style.transform = `translateX(0vw)`;
}
});
마우스를 땠을 때 이동거리가 100 이상이면 2번 사진을, 100 미만이면 1번 사진으로 돌아오도록 기능 구현하였다.
이동거리 구하는 방법
e.clientX - startCoordinate
사실 아직 사진이 자연스럽게 움직이지 않는다 그래서 17강 마지막에 결과를 보여주지 않았다,,,
왜 순간이동 하냐면
마우스 떼면 2번사진으로 이동하라고 코드짰기 때문이다!
서서히 이동하는 애니메이션 추가는 안했으니까!
애니메이션주고 싶으면 이동할 박스에 css transition 추가하면 된다.
근데 문제가 있는데
이전 강의에서 기능1 만들 때 불편해서 .slide-container 박스에 있던 transition을 제거했다.
왜냐면 누르고 사진을 스와이프할 때는 transition이 있으면 느리게 동작했기 때문에!
transition이 필요가 없는데transition이 필요하다.그럼 "마우스 떼면 잠깐 0.5초정도 transition 붙였다가 떼주세요"
라고 코드짜면되지않을까?
예전에 배운 타이머같은거 활용하면 가능합니다. transition 붙여놓고 0.5초 후에 떼라고 하면 될 것 같다.
slideBox.addEventListener("mouseup", function (e) {
pressDown = false;
if (e.clientX - startCoordinate < -100) {
slideContainer.style.transform = `translateX(-100vw)`;
slideContainer.style.transition = `all 0.5s`;
} else {
slideContainer.style.transform = `translateX(0vw)`;
slideContainer.style.transition = `all 0.5s`;
}
setTimeout(() => {
slideContainer.style.transition = "none";
}, 500);
});
이런식으로 코드 짜면
마우스를 떼면 0.5초 후에 transition 효과가 사라진다.
사이트를 모바일기기로 테스트하고 싶으면 크롬개발자도구 좌상단 toggle device toolbar 버튼누르면 된다.

근데 모바일기기로 테스트해보면 스와이프가 안된다.
왜냐면 마우스이벤트리스너를 달아놨기 때문이다.
모바일은 터치이벤트리스너를 달아줘야 터치에 반응한다.
touchstart (터치시작시 발동)touchmove (터치중일 때 계속 발동)touchend (터치종료시 발동)이런 이벤트를 듣는 리스너를 부착하면 이제 터치스와이프 가능하다.
let startCoordinate = 0;
let pressDown = false;
let pressNot = false;
slideBox.addEventListener("touchstart", function (e) {
startCoordinate = e.touches[0].clientX;
pressDown = true;
});
slideBox.addEventListener("touchmove", function (e) {
if (pressDown == true) {
slideContainer.style.transform = `translateX(${
e.touches[0].clientX - startCoordinate
}px)`;
}
});
slideBox.addEventListener("touchend", function (e) {
pressDown = false;
if (e.touches[0].clientX - startCoordinate < -100) {
slideContainer.style.transform = `translateX(-100vw)`;
slideContainer.style.transition = `all 0.5s`;
} else {
slideContainer.style.transform = `translateX(0vw)`;
slideContainer.style.transition = `all 0.5s`;
}
setTimeout(() => {
slideContainer.style.transition = "none";
}, 500);
});
기존 코드를 이렇게 바꾸면 된다.
그럼 모바일에서도 아까랑 똑같이 동작하는데 주의사항은
e.clientX를 e.touches[0].clientX 이걸로 바꾸면 된다.
왜냐면 터치는 여러 손가락으로 할 수 있어서 그 중 몇번째 손가락인지 지정해줘야한다.
touchend 이벤트리스너에선 e.clientX 말고 e.changedTouches[0].clientX 쓰면 된다.

자바스크립트는 외부 라이브러리 의존도가 언제나 높은데
Hammer.js 같은거 가져다쓰면 조금 쉽게 기능개발이 가능하다.
스와이프, pinch, rotate 등 여러 제스쳐를 감지하는 이벤트리스너 제공해서 편리하고slide.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
crossorigin="anonymous" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="./slide.css" />
<title>Document</title>
</head>
<body>
<div style="overflow: hidden">
<div class="slide-container">
<div class="slide-box">
<img src="./assets/car1.png" />
</div>
<div class="slide-box">
<img src="./assets/car2.png" />
</div>
<div class="slide-box">
<img src="./assets/car3.png" />
</div>
</div>
</div>
<button class="btn1">1</button>
<button class="btn2">2</button>
<button class="btn3">3</button>
<button class="prev"><</button>
<button class="next">></button>
<script src="./slide.js"></script>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
crossorigin="anonymous"></script>
</body>
</html>
slide.js
const slideContainer = document.querySelector(".slide-container");
const slideBox = document.querySelector(".slide-box");
const carouselBtn1 = document.querySelector(".btn1");
const carouselBtn2 = document.querySelector(".btn2");
const carouselBtn3 = document.querySelector(".btn3");
const carouselNext = document.querySelector(".next");
const carouselPrev = document.querySelector(".prev");
// carouselBtn1.addEventListener("click", handleCarousel1);
// carouselBtn2.addEventListener("click", handleCarousel2);
// carouselBtn3.addEventListener("click", handleCarousel3);
// carouselNext.addEventListener("click", handleCarouselNext);
// carouselPrev.addEventListener("click", handleCarouselPrev);
// //캐러쉘 1번
// let nowPhoto = 0;
// function handleCarousel1() {
// nowPhoto = 0;
// slideContainer.style.transform = `translateX(-${nowPhoto}00vw)`;
// }
// //캐러쉘 2번
// function handleCarousel2() {
// nowPhoto = 1;
// slideContainer.style.transform = `translateX(-${nowPhoto}00vw)`;
// }
// //캐러쉘 3번
// function handleCarousel3() {
// nowPhoto = 2;
// slideContainer.style.transform = `translateX(-${nowPhoto}00vw)`;
// }
// //캐러쉘 Next버튼
// function handleCarouselNext() {
// if (nowPhoto == 0) {
// nowPhoto += 1;
// slideContainer.style.transform = `translateX(-${nowPhoto}00vw)`;
// } else if (nowPhoto == 1) {
// nowPhoto += 1;
// slideContainer.style.transform = `translateX(-${nowPhoto}00vw)`;
// }
// }
// //캐러쉘 Prev버튼
// function handleCarouselPrev() {
// if (nowPhoto == 2) {
// nowPhoto -= 1;
// slideContainer.style.transform = `translateX(-${nowPhoto}00vw)`;
// } else if (nowPhoto == 1) {
// nowPhoto -= 1;
// slideContainer.style.transform = `translateX(-${nowPhoto}00vw)`;
// }
// }
let startCoordinate = 0;
let pressDown = false;
let pressNot = false;
slideBox.addEventListener("touchstart", function (e) {
startCoordinate = e.touches[0].clientX;
pressDown = true;
});
slideBox.addEventListener("touchmove", function (e) {
if (pressDown == true) {
slideContainer.style.transform = `translateX(${
e.touches[0].clientX - startCoordinate
}px)`;
}
});
slideBox.addEventListener("touchend", function (e) {
pressDown = false;
if (e.changedTouches[0].clientX - startCoordinate < -100) {
slideContainer.style.transform = `translateX(-100vw)`;
slideContainer.style.transition = `all 0.5s`;
} else {
slideContainer.style.transform = `translateX(0vw)`;
slideContainer.style.transition = `all 0.5s`;
}
setTimeout(() => {
slideContainer.style.transition = "none";
}, 500);
});
slide.css
.slide-container {
width: 300vw;
}
.slide-box {
width: 100vw;
float: left;
}
.slide-box img {
width: 100%;
}