swiper.js 없이 swipe 만들어보기 (1)

kyu·2021년 7월 18일
26
post-thumbnail

1. swipe란?

swipe란 손가락을 댄 후, 일직선으로 드래그하는 것을 말한다.

2. 그래서 무엇을 만들 것이냐?

상단 gif와 같다.

3. swiper.js 있는데 왜 만드냐?

(1) 공부용
(2) 경량화
(3) 문서 읽기 싫음 (이미 읽음)
(4) 리액트에서 쉽게 사용하기 위해서 react-id-swiper를 다운받아야 함

그렇다고 이 문서가 react로 포팅된 swiper는 아님.

4. 시작합니다.

HTML

먼저 DOM 틀부터 필요하다.
잇몸이 있어야 이를 끼워넣든 하지 않겠는가

<div id="container">
    <div id="inner">
      ... 다량의 이미지 태그
    </div>
  </div>

왜 두번 div로 감쌀까?

container는 넘치는 컨텐츠를 보여주지 않기 위해
overflow: hidden 을 사용한다.

inner는 실제 컨텐츠를 담는 공간이다.
container가 보여주지 않는 공간(넘치는 공간)을 다 담는 것이다.

개발자 도구를 켜서 inner부분을 잡아보면 container와 크기가 같은 것을 볼 수 있다.
div는 display: block 속성을 기본으로 가지고 있고,
이는 상위 객체의 width를 따라간다.

그래서 overflow: hidden인 container와, inner 모두 같은 px이다.


CSS

body {
  margin: 0;
}

#container {
  height: 300px;
  overflow: hidden;
}

#inner {
  display: flex;
  height: 300px;
  transition-property: transform;
}

img {
  width: 100%;
}

body에 보기 싫은 margin이 8px이나 껴있으니 빼주자.

inner에 display: flex 를 준 이유는 이미지를 일렬로 나열하기 위함이다.

display: flex 의 기본 flex-directionrow 이기 때문이다.

transition-property: transform 트랜지션은 transform이 일어날 때만 사용하겠다는 뜻이다.

이렇게 하면 한 화면에 하나의 이미지가 보일 것이다. (모바일 기준)

Javascript

먼저 eventlistener의 세 종류를 알아야 한다.

window.addEventListener('touchstart', callback);
window.addEventListener('touchmove', callback);
window.addEventListener('touchend', callback);

세 가지 이벤트 모두 터치 위치를(스크린에 올려둔 마우스의 포인터 기준) 알 수 있고,
각각의 이름과 같이 touchstart 는 터치를 시작한 시점 touchmove 는 움직이고 있을 당시
touchend 는 터치가 끝난 시점을 잡아낸다.

나는 container 라는 이름을 가진 div가 스와이프 됨에 따라
inner가 보여주는 슬라이드가 변하는 것을 원하기 때문에
container에 eventListener 를 부여하기로 했다.

const container = document.getElementById('container');
const inner = document.getElementById('inner');

let startPos = 0;
let offset = 0;
let curPos = 0;
const screenWidth = container.clientWidth;

전역 변수로 다음과 같이 지정하였다.

startPos는 touchstart 지점에서 찍히는 screenX를 뜻한다.
offset은 touch를 시작하고 나서의 변위를 뜻한다.
curPos는 현재 슬라이드의 위치, inner가 마지막에 움직이는 위치를 위해 정의했다.

window.onload = function() {
  여기에 밑의 세가지 체크포인트가 들어간다.
}

checkpoint 1

먼저 우리는 터치를 드래그 함으로써 이미지를 넘길 것이기 때문에 터치를 시작한 위치를 알아야 한다.

container.addEventListener('touchstart', (e) => {
  startPos = e.touches[0].pageX;
});

checkpoint 2

스와이프를 끝마친 다음에야 슬라이드가 넘어가는 비(非)유동적인 swiper를 만들지 않기 위해서는
이미지가 내가 스와이프 하고 있는 동안 을 따라와야 한다.

따라서 경위(offset)를 구해주는데 여기에 curPos를 더한 이유는
현재 몇번째 슬라이드에서 스와이프를 하고 있는지를 적용하기 위해서다.

긴 말 할 것 없다. transform으로 x값을 translate해주자.
이 때 transitionDuration은 바로바로 드래그에 이미지가 딸려오게 하기 위함이다.

container.addEventListener('touchmove', (e) => {
  offset = curPos + (e.targetTouches[0].pageX - startPos);
  inner.style.transform = `translate3d(${offset}px, 0px, 0px)`;
  inner.style.transitionDuration = '0ms';
});

checkpoint 3

앞과 마찬가지로 변위(offset)를 구해주자.

destination을 따로 구한 이유는

앞이나 뒤의 일정 공간을 더 끌어도 결국에는 마지막 혹은 첫번째 슬라이드로 돌아오게 하기 위함이다.

Math.round를 해준 이유는 반 이상 이미지를 드래그 했냐고 물어보는 것이다.

container.addEventListener('touchend', (e) => {
  const sum = curPos + (e.changedTouches[0].pageX - startPos);
  let destination = Math.round(sum / screenWidth) * screenWidth;
  if (destination > 0) {
    destination = 0;
  }
  else if (destination < -(screenWidth * (이미지의 총 갯수 - 1))) {
    destination = -(screenWidth * (이미지의 총 갯수 - 1));
  }

  inner.style.transform = `translate3d(${destination}px, 0px, 0px)`;
  inner.style.transitionDuration = '300ms';
  curPos = destination;

  setTimeout(() => {
    inner.style.transitionDuration = '0ms';
  }, 300);
});

근데, 돌아올 때 0ms만에 돌아오면 뒤의 여백을 땡긴 의미가 없지 않는가?

transitionDuration 을 300ms로 바꾸어주고,

300ms 후에 다시 0ms으로 바꾸어준다.


막 자신있게 이야기해서 마치 라이브러리 하나를 위의 코드로 단축한 것 같지만,
굉장히 중요한 기능들은 구현하지 못했다.

예를 들면 드래그 한 범위가 좁더라도 그 스피드가 빠르면 스크롤 스피드를 적용하여
가중치를 곱해주어야 꼭 반이 넘지 않아도 슬라이드가 넘어가는데,

touch의 callback parameter를 유심히 찾아보았으나, 그런 속성은 찾지 못하였다.

혹시 알고 있다면 댓글에 써주길 간곡히 요청한다. ㅠㅠ

profile
Javascript를 공부하는 kyu입니다.

6개의 댓글

comment-user-thumbnail
2021년 7월 18일

😎😎

답글 달기
comment-user-thumbnail
2021년 7월 26일

잘 읽었습니다! 그런데 크롬 개발자도구에서 모바일 화면으로 전환해서 테스트 해봤는데
이미지가 갯수대로 다 붙어서 나오네요!! 원래 한 개만 나오고 나머지 이미지는 안 보이는게 아닌가요?

1개의 답글
comment-user-thumbnail
2021년 8월 3일

잘봤숩니당. ><

1개의 답글
comment-user-thumbnail
2022년 3월 23일

멋집니다! 프로젝트에 진행하는데 도움이 되었어요!

답글 달기