Swiper 로 잠금화면 만들기

차차·2024년 2월 20일
2
post-thumbnail

Swiper

모바일(터치) + 웹(클릭) 환경에서 작동하는 슬라이더 라이브러리

react 에서 사용하기

리액트 컴포넌트로 사용하기 위해서는, SwiperSwiperSlide 라는 컴포넌트와 swiper/css 스타일을 import 해서 작성해야 한다. 그러면 위 그림과 같이 가장 기본적인 슬라이드 UI 가 완성된다 !

import { Swiper, SwiperSlide } from 'swiper/react';
import 'swiper/css';

const Example = () => {
  return (
    <Swiper>
      <SwiperSlide>Slide 1</SwiperSlide>
      <SwiperSlide>Slide 2</SwiperSlide>
      <SwiperSlide>Slide 3</SwiperSlide>
      <SwiperSlide>Slide 4</SwiperSlide>
    </Swiper>
  );
};

약간의 스타일을 커스텀하기 위해서는 className 을 활용하면 된다.
중요한 것은 슬라이드가 바뀔 때 마다 해당 Element 가 mount/unmount 되는 것이 아니라는 점이다.
쉽게 말해서 모든 슬라이드는 mount 되어 있는 상태이며, 그냥 컨테이너 밖에 숨어있는 것이다.

overflow: hidden.swiper 가 있고, 자식인 .swiper-wrapper 가 x축으로 이동되면서 .swiper-slide가 하나씩 보여지는 방식이다. 이러한 방식임을 이해해야, 여러 UI 에 최대로 써먹을 수 있다.

또한 각각의 슬라이드에 데이터 요청이 이루어지는 컴포넌트가 있다고 가정하면, 결국 모든 슬라이드에서 데이터 요청이 발생하게 된다. 따라서 성능도 잘 생각해야 한다!


각종 기능 추가하기

기본적인 슬라이드에, Pagination, Navigation 과 같은 여러 기능들을 추가해줄 수도 있다. 그 친구들은 swiper/modules 에서 추가적으로 import 해와야 한다.

모듈을 추가하려면, 몇 가지를 해주어야 한다.

  • 모듈 import 해오기
	import { Navigation } from 'swiper/modules'
  • 스타일 import 해오기
	import 'swiper/css/navigation'
  • Swiper Component 에 넣어주기
  • 옵션 설정해주기 또는 true 전달
	<Swiper 
      modules={[Navigation]}
      navigation={/*...*/}
    >
	// ...
	</Swiper>

예시로, 슬라이드 앞/뒤 이동 버튼을 만드는 Navigation 모듈과 하단 bullet 버튼을 생성하는 Pagination 을 적용한다면!

import { Pagination, Navigation } from 'swiper/modules';
import 'swiper/css/pagination';
import 'swiper/css/navigation';

const Example = () => {
  return (
    <Swiper
      modules={[Pagination, Navigation]}
      navigation
      pagination={{ clickable: true }}>
      <SwiperSlide>Slide 1</SwiperSlide>
      <SwiperSlide>Slide 2</SwiperSlide>
      <SwiperSlide>Slide 3</SwiperSlide>
      <SwiperSlide>Slide 4</SwiperSlide>
    </Swiper>
  );
};

이 외에도, 각종 모듈에 모듈에 대한 옵션이 상당히 다양하게 구현되어 있다! 버튼 추가, 슬라이드 이동 제어 (마우스휠, 키보드 등), 전환 효과, DOM 마운트 제어, 지연 로딩 등등 많은 기능을 사용할 수 있다.
Swiper Modules 보러가기


Swiper 응용

여기까지는 Swiper 를 기본적으로 사용하기 위한 방법이다. 가장 보편적인 사용 사례이기도 하다!
Moabam 프로젝트에서는 이 Swiper 친구를 활용하여 다양한 UI 를 구성하였다.

잠금화면같은 UI 만들기

모바일 디바이스의 잠금화면에서 영감을 얻어 기획된 UI 로, swipe-up 인터랙션을 통해 페이지가 이동되어야 했다.
또한, 모바일의 터치 스와이프와 PC 의 클릭 드래그가 모두 작동해야 했다.

요구사항을 정리해보자면,

  • 랜딩페이지(/)에서 위로 swipe 시 루틴페이지(/routines)로 이동한다.
  • swipe 하는 동안에는 루틴페이지가 아래에 깔려 있어야 한다.
  • 모바일과 웹에서 동일하게 작동한다.

작동 방식 1. 텅 빈 슬라이드 두기

이 UI 의 최고로 어려웠던 점은, 루틴페이지가 아래에 깔려있고 랜딩페이지가 덮개 역할을 해야한다는 점이었다. 게다가 swipe 와 동시에 페이지 이동이 이루어져야 한다니..

’투명한 덮개’ 라고 생각하면 되지 않을까? 라는 아이디어에서 해결할 수 있었다.

위로 swipe 해야하는 랜딩페이지는 슬라이드 2개를 가진 Swiper 가 있으면 된다.
1. 실제 컨텐츠가 보이는 슬라이드
2. 텅 빈 슬라이드

따라서, 위로 스와이프하는 동안에는 텅 빈 슬라이드 아래에 깔린 루틴페이지가 보인다. 핸드폰 잠금화면처럼 보이는 것이다!


작동 방식 2. onReachEnd 속성 사용

사용자가 화면을 위로 스와이프하여 투명 슬라이드로 전환되면, 페이지 이동(//routines)이 발생한다.

이는 Swiper 의 onReachEnd 속성을 활용해서 쉽게 구현할 수 있었다. (= 공식문서 뒤적뒤적..)

말그대로 마지막 슬라이드에 도달했을 때 실행해야 하는 동작을 설정해주면 된다.

코드로 표현하면 다음과 같다.

const StartPage = () => {
	return (
		<>
			// 실제 RoutinesPage 미리 깔아놓기
			<div className="absolute h-full w-full">
              <RoutinesPage dayType={dayType} />
            </div>
			 // 덮개 Swiper
            <Swiper
              className="h-full"
              direction="vertical"
              allowSlidePrev={false}
              onReachEnd={() => moveTo('routines')} // 페이지 이동
              threshold={50} 
            >
              <SwiperSlide className="shadow-lg">
                // 실제 StartPage의 컨텐츠가 있는 슬라이드
              </SwiperSlide>
              <SwiperSlide></SwiperSlide> // 텅 빈 슬라이드
            </Swiper>
		</>
	)
}
  • direction : 슬라이드 방향, 위로 스와이프해야 하기 때문에 vertical 로 설정했다. (기본값은 horizontal)
  • allowSlidePrev : 위로 올리는 인터랙션만 허용해야하기 때문에 아래로 스와이프하는 것은 비활성화(false) 해주었다.
  • onReachEnd : 마지막 슬라이드 도달 시 실행할 동작을 넣어줄 수 있는 프로퍼티, 루틴페이지로 이동하는 함수를 넣어줬다.

문제 발생!

문제가 발생했다! 무조건 데이터 요청이 중복으로 일어났다! (당연함..)
랜딩페이지에서도 아래에 깔린 루틴페이지가 마운트 되기 때문이다.
(루틴페이지에서는 유저가 참여중인 루틴 그룹을 불러온다.)

이러한 문제는 SkeletonUI 를 작성한 후에 쉽게 해결할 수 있었다.
SkeletonUI 만 나오는 정적 컴포넌트인 FakeRoutinesPage 를 작성했다.

const FakeRoutinesPage = ({ dayType }: FakeRoutinesPageProps) => {
  return (
    <div className="flex h-full flex-col items-center overflow-auto">
      <div className="mb-4 mt-8 flex w-full items-center justify-between px-10 pr-8">
        <DayInfo dayType={dayType} />
        <div className="h-10 w-14"></div>
      </div>

      <div className="h-full w-full">
        <div className="h-full overflow-auto px-8 pt-1">
          <RoomDataFallback />  // 실제 데이터가 필요한 부분은 SkeletonUI 렌더링
        </div>
      </div>
    </div>
  );
};

export default FakeRoutinesPage;

그리고 원래 루틴페이지가 있던 부분은 이 페이크 친구로 바꿔줬다.

const StartPage = () => {
  return (
    <>
        // 실제 페이지 컴포넌트가 아닌, FakeRoutinesPage 깔아놓기
        <div className="absolute h-full w-full">
          <FakeRoutinesPage dayType={dayType} />
        </div>

    // 나머지는 원래와 동일

따라서 위로 스와이프하는 동안, 실제 루틴페이지가 아닌 SkeletonUI 만 들어있는 가짜 루틴 페이지가 아래에 렌더링된다.
/routines 로 이동하여 실제 루틴페이지가 렌더링되면, 데이터를 불러오게 되고 로딩 중엔 SkeletonUI 가 나타난다.

정리하면 이런 느낌!
시작페이지 ➡️ 스와이프 + Skeleton루틴페이지 ➡️ Skeleton 루틴페이지 ➡️ 서버 데이터 루틴페이지

따라서 사용자 입장에서는 스와이프하는 동안에 루틴페이지를 로딩 중인 것 처럼 보이는 것이다.
이로써 아래와 같이 감쪽같은 페이지 전환을 구현할 수 있었다.


슬라이드 토글버튼 만들기

루틴 페이지는 이렇게 아침, 밤 슬라이드가 있는 페이지이다.
이 페이지에서 스와이프가 가능한 지 모르지 않을까? 라는 의견이 발생했고, 이 슬라이드 친구를 제어하는 토글 버튼이 디자인에 추가되었다.

아침-밤은 자연스럽게 넘어가는데 토글 버튼은 딱딱 끊겨서 바뀌게 된다면 부자연스러울 것이다. 그래서 부드럽게 슬라이드 되는 토글 버튼을 구현했다!


작동 방식

슬라이드 되는 토글 버튼의 원리는..간단하다!
이 친구도 Swiper 이다.

사실 이렇게 토글 버튼이 쪼개져 있는 형태이다. 각각의 슬라이드는 토글 버튼 반쪽을 나누어 가진 것이다.

하지만 뭔가 생각한 토글 버튼과는 방향이 반대이다.
토글 버튼은 기본적으로 버튼이 왼쪽에서 시작한다.

Swiper 의 속성 중 dir 을 활용하면, 스와이프 방향을 정해줄 수 있다. 여기에 rtl 을 넣어주면 모든 방향이 반대로가 된다! (right to left)

코드로 표현하면 다음과 같다.

<Swiper>
  <SwiperSlide dir='rtl'>
    slide1
    <div className='btn btn-1'>button</div>
  </SwiperSlide>
  <SwiperSlide>
    slide2
    <div className='btn btn-2'>button</div>
  </SwiperSlide>
</Swiper>

마지막으로, 좀 더 버튼스럽게 하기 위해 뚜껑을 덮어주면 된다. 당연히 CSS 도 건드려야 한다!
(.swiper 에게 overflow:visible 주기)

<div className='container'>
  <Swiper dir='rtl'>
    <SwiperSlide>
      <div className='btn btn-1'>
        <div className='toggle toggle-1'></div>
      </div>
    </SwiperSlide>
    <SwiperSlide>
      <div className='btn btn-2'>
        <div className='toggle toggle-2'></div>
      </div>
    </SwiperSlide>
  </Swiper>
</div>
.container {
  width: 600px;
  overflow: hidden;
}

.swiper {
  width: 400px;
  height: 200px;
  border: 1px solid black;
  overflow: visible;
}

.swiper-slide {
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}

.btn {
	position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100px;
  height: 100%;
}

.btn-1 {
  left: 0;
}
.btn-2 {
  right: 0;
}

.toggle {
  position: absolute;
  width: 200px;
  height: 100%;
  background-color: rgb(126, 159, 255);
  border-radius: 100%;
}

.toggle-1 {
  right: 0;
}
.toggle-2 {
  left: 0;
}

이렇게 토글 버튼이 완성되면, Controller 모듈(스와이퍼A 넘기면 스와이퍼B도 넘어가도록 하는 기능)을 사용해서 두 스와이퍼를 서로 연결해준다. 그러면 완성!

크게 보면 아래와 같이 토글 버튼과 아침-밤 슬라이드이 잘 동기화되어 작동하는 것을 볼 수 있다 ✨


하지만 이 친구들은 이제 react component 방식에서 web component 방식으로 마이그레이션되어야 한다. 하하하

0개의 댓글