[Vanilla JS] 바텀 시트(Bottom Sheet) 구현하기

Kim Dong Hyun·2023년 5월 20일
1

Front-End

목록 보기
4/4

HTML

<div class="up_sensor"></div>
  <div id="bottomSheet" class="bottom_sheet">
    <div class="bottom_sheet_handle_wrap">
      <div class="bottom_sheet_handle"></div>
    </div>
    <div style="margin-bottom: 40px;"></div>
    <div class="bottom_box">
      <!-- 바텀시트 내용 -->
    </div>
  </div>
</div>

up_sensor는 바텀시트를 올리기 위한 동작을 감지하기 위한 센서이다.
실제 바텀 시트 위에 감싸고 있는 투명한 벽으로, 바텀시트가 올라가면 사라진다.
센서를 두지 않으면 바텀 시트를 올리는 과정에서 스크롤 되면서 안의 내용이 스크롤 되는 것을 방지해서 만들었다.

bottom_sheet_handle은 사용자에게 바텀시트를 올리고 내릴 수 있는 hint를 제공한다.




CSS

.bottom_sheet {
   display: flex;
   position: fixed;
   bottom: 0;
   width: 100%;
   height: 10%;
   border-top-left-radius: 30px;
   border-top-right-radius: 30px;
   background-color: #FFFFFF;
   overflow-y: auto;
   flex-direction: column;
   align-items: center;
   transition-duration: 1s;
}

.bottom_sheet_handle_wrap {
   z-index: 1;
   background: linear-gradient(to bottom,
           rgba(255, 255, 255, 1) 20%,
           rgba(255, 255, 255, 0.75) 35%,
           rgba(255, 255, 255, 0.5) 60%,
           rgba(255, 255, 255, 0.25) 85%,
           rgba(255, 255, 255, 0) 100%);
   display: flex;
   justify-content: center;
   align-items: flex-start;
   width: 80%;
   height: 40px;
   margin-bottom: 10px;
   position: fixed;
}

.bottom_sheet_handle {
   width: 20%;
   height: 10px;
   background-color: #7c7979;
   border-radius: 30px;
   margin-top: 10px;
}

.bottom_box {
   width: 80%;
   height: auto;
   margin-top: 10px;
   margin-bottom: 10px;
   padding: 5px;
   border: 1px solid lightgray;
   border-radius: 10px;
   transition-duration: 2s;
   padding-top: 20px;
   padding-bottom: 20px;
}

.up_sensor {
   z-index: 1;
   position: absolute;
   background: transparent;
   width: inherit;
   height: 100%;
   bottom: 0;
   height: 10%;
   transition-duration: 1s;
}

up_sensor는 바텀시트보다 z-index가 커야하고, background가 transparent여야 한다.

바텀시트의 height값을 중점으로 올리고 내리기 때문에, height값을 중점적으로 다뤄야 한다.

바텀 시트의 transition-duration값을 통해서 자연스럽게 올라가고 내려감을 표현해야 한다.

JS


var handle_wrap = document.getElementsByClassName('bottom_sheet_handle_wrap')[0];
var bottom_sheet = document.getElementsByClassName('bottom_sheet')[0];
var up_sensor = document.getElementsByClassName('up_sensor')[0];
let bottom_touch_start = 0;
let bottom_scroll_start;

//up_sensor에서 터치가 움직였을 경우 (바텀시트를 건드렸을 경우) -> 바텀시트를 올림
up_sensor.addEventListener("touchmove", (e) => {
  bottom_sheet.style.height = 70 + "%" //바텀시트 height를 올리기 10% -> 70%
  up_sensor.style.height = 70 + "%"; //up_sensor도 따라가기
  setTimeout(function () {
    up_sensor.style.display = "none";
  }, 1000); // 바텀시트가 올라간 후, up_sensor 사라지기
});


//맨 위에서 아래로 스크롤했을 경우
bottom_sheet.addEventListener("touchstart", (e) => {
  bottom_touch_start = e.touches[0].pageY; // 터치가 시작되는 위치 저장
  bottom_scroll_start = bottom_sheet.scrollTop //터치 시작 시 스크롤 위치 저장
});

bottom_sheet.addEventListener("touchmove", (e) => {
  //유저가 아래로 내렸을 경우 + 스크롤 위치가 맨 위일 경우
  if (((bottom_touch_start - e.touches[0].pageY) < 0) && (bottom_scroll_start <= 0)) {
    //바텀시트 내리기
    bottom_sheet.style.height = 10 + "%"
    up_sensor.style.display = "block"; //up_sensor 다시 나타나기
    up_sensor.style.height = "10%"; //up_sensor height 다시 지정
  };
});


//맨 위 핸들을 아래로 당겼을 경우
handle_wrap.addEventListener("touchstart", (e) => {
  bottom_touch_start = e.touches[0].pageY; // 터치가 시작되는 위치 저장
});
handle_wrap.addEventListener("touchmove", (e) => {
  //사용자가 아래로 내렸을 경우
  if ((bottom_touch_start - e.touches[0].pageY) < 0) {
    //바텀시트 내리기
    bottom_sheet.style.height = 10 + "%"
    up_sensor.style.display = "block"; //up_sensor 다시 나타나기
    up_sensor.style.height = "10%"; //up_sensor height 다시 지정
  };
});

사용자가 아래로 내렸는지 검사하는 과정 때문에 코드가 좀 길어진다.


바텀시트가 올라가는 경우 :
1. 사용자가 up_sensor를 위로 올렸을 경우 (실제 코드는 그냥 움직이기만 해도 올라간다.. 위로 올리는 경우로 구현해도 좋다)


바텀시트가 내려가는 경우 :
1. 스크롤 맨 위에서 아래로 스크롤 했을 경우
2. 바텀시트 핸들을 아래로 내렸을 경우


구글링을 해봐도 React로 구현한 경우가 대다수라 Vanilla JS로 구현해 보았다.
코드도 길고 복잡하지만 최대한 간결하게 써봤다!!!

1개의 댓글

comment-user-thumbnail
2023년 12월 4일

좋은 참고자료 감사합니다~!

답글 달기