ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Click and Drag to Scroll
    Web/JS30 2020. 12. 26. 21:56
    반응형

    웹서핑을 하다보면, 마우스로 드래그해서 스크롤을 할 수 있게끔 해 놓은 페이지들을 종종 볼 수 있다.

    마치 터치를 하는 것과 비슷한 조작감을 주곤 하는데, 오늘은 이 드래그 스크롤을 구현해보도록 하자.

    HTML 코드는 간단하다.

    items라는 클래스를 가진 div 안에 item이라는 클래스를 가진 div들이 총 25개 들어있다.

    <div class="items">
      <div class="item item1">01</div>
      <div class="item item2">02</div>
      <div class="item item3">03</div>
      <div class="item item4">04</div>
      <div class="item item5">05</div>
      <div class="item item6">06</div>
      <div class="item item7">07</div>
      <div class="item item8">08</div>
      <div class="item item9">09</div>
      <div class="item item10">10</div>
      <div class="item item11">11</div>
      <div class="item item12">12</div>
      <div class="item item13">13</div>
      <div class="item item14">14</div>
      <div class="item item15">15</div>
      <div class="item item16">16</div>
      <div class="item item17">17</div>
      <div class="item item18">18</div>
      <div class="item item19">19</div>
      <div class="item item20">20</div>
      <div class="item item21">21</div>
      <div class="item item22">22</div>
      <div class="item item23">23</div>
      <div class="item item24">24</div>
      <div class="item item25">25</div>
    </div>

    우선, 변수 선언부는 다음과 같다.

    const slider = document.querySelector('.items');
    let isDown = false;
    let startX;
    let scrollLeft;

    아이템들을 포함하고 있는 가장 상위 divslider라는 변수에 담았고,

    마우스를 클릭한 상태인지를 나타내는 isDown 변수,

    맨 처음 마우스를 클릭한 위치를 나타내는 startX,

    그리고 가로 스크롤 바의 왼쪽 기준 위치를 나타내는 scrollLeft 변수를 선언해놓았다.

     

    이벤트 리스너는 총 네 개가 필요하다.

    마우스를 처음 클릭할 때, 마우스가 지정 영역 바깥으로 나갈 때,

    마우스 클릭을 놓을 때, 그리고 마우스를 움직일 때를 대비한 네 개의 이벤트 리스너를 달아준다.

    slider.addEventListener('mousedown', e => {
      isDown = true;
      slider.classList.add('active');
    });
    
    slider.addEventListener('mouseleave', () => {
      isDown = false;
      slider.classList.remove('active');
    });
    
    slider.addEventListener('mouseup', () => {
      isDown = false;
      slider.classList.remove('active');
    });
    
    slider.addEventListener('mousemove', e => {
      if (!isDown) return;
    });

    마우스를 클릭할 때 isDown이 true가 되게 하고, 마우스가 지정 영역(items 클래스를 가진 div)을 떠나거나

    클릭했던 마우스를 놓을 때 isDown이 false로 다시 되돌아오도록 한다.

     

    그리고 mousemove 이벤트는 마우스를 클릭하고 있을 때만 동작하도록 if문을 달아준다.

    마우스를 클릭하지 않을 때도 계속 함수를 실행시키면 쓸데없는 연산이 발생하기 때문.

     

    참고로 active 클래스는 items div를 살짝 크게 만들어주는 CSS가 붙어 있다.


    다른 함수들은 이제 건드릴 필요가 없고, mousedownmousemove의 내용만 더 보충해주면 된다.

    일단, 마우스를 드래그해서 스크롤이 움직이도록 하기 위해서는

    맨 처음 마우스를 클릭할 당시마우스의 위치스크롤의 위치를 저장해놓아야 한다.

    그래야만, 그 둘을 기준으로 마우스가 얼마만큼 움직였는지를 알아내서 스크롤을 움직일 수 있기 때문.

    slider.addEventListener('mousedown', e => {
      isDown = true;
      slider.classList.add('active');
      startX = e.pageX - slider.offsetLeft;
      scrollLeft = slider.scrollLeft;
    });

    미리 만들어두었던 startX라는 변수에 처음 마우스를 클릭했을 때의 마우스 위치를 저장하고,

    scrollLeft 변수에 스크롤의 위치를 저장했다.

     

    우리가 클릭 이벤트에서 사용할 수 있는 위치 속성은 총 4가지가 있다.

    바로 (clientX, clientY), (offsetX, offsetY), (pageX, pageY), (screenX, screenY)가 그것이다.

     

    clientX, clientY

    브라우저 페이지의 왼쪽 상단 모서리를 기준으로 하는 X좌표, Y좌표를 반환한다.

    뷰포트의 왼쪽 가장자리를 클릭하면, 페이지가 수평으로 스크롤 되든 말든 항상 clientX가 0으로 나온다.

    (즉, 스크롤하기 전에는 보이지 않는 영역을 고려하지 않는다)

     

    offsetX, offsetY

    이벤트가 발생한 대상 요소가 기준이 된다.

    해당 요소의 왼쪽 상단을 기준으로 한 X좌표와 Y좌표이다.

     

    pageX, pageY

    페이지의 왼쪽 상단 모서리를 기준으로 하는 X좌표, Y좌표이다.

    사용자가 브라우저 내에서 스크롤 막대를 움직여도 해당 X좌표, Y좌표는 변하지 않는다.

    뷰포트의 왼쪽 가장자리를 클릭하면, 페이지가 수평으로 스크롤 된 만큼 clientX가 증가해서 나온다.

    (즉, 스크롤하기 전에는 보이지 않는 영역까지 모두 고려를 한 좌표이다)

     

    screenX, screenY

    실제 화면, 즉 모니터 왼쪽 상단을 기준으로 하는 X좌표, Y좌표의 위치를 의미한다.

    브라우저 화면이 아니라 모니터 전체 화면을 기준으로 한 위치 좌표이다.

    모니터의 수나 모니터 해상도를 바꿀 때에만 변한다고 한다.

     

    여기서는 페이지 전체 기준 X 좌표(pageX)에서 items div의 왼쪽 가장자리의 X 좌표만큼을 빼주는 방법을 선택했다.

    그렇게 하면 items div의 왼쪽 가장자리를 기준으로 한 X 좌표를 가져올 수가 있기 때문.

     

    offsetX를 사용하지 않은 이유는, 이벤트가 발생하는 요소가

    items 전체 요소가 아니라 내부에 포함된 item 각각이 될 수 있기 때문이다.


    마지막으로 mousemove의 이벤트 리스너이다.

    slider.addEventListener('mousemove', e => {
      if (!isDown) return; 
      e.preventDefault();
      const x = e.pageX - slider.offsetLeft;
      const walk = x - startX; 
      slider.scrollLeft = walk;
    });

    e.preventDefault를 해준 이유는, 

    마우스를 움직이면서 지나가는 경로에 있는 텍스트같은 것들을 클릭하게 되는 현상을 방지하기 위해서라고 한다.

     

    변수 x는 위에서 startX를 계산했던 것과 똑같은 방식으로 계산했다는 것을 알 수 있는데,

    마우스가 이동할 때마다 매번 마우스의 위치를 새로 잡아주는 것이다.

    그 이유는, 이동하고 난 마우스의 위치에서

    맨 처음 클릭할 때의 마우스 위치를 빼서 walk라는 변수에 담았는데

    walk의 양만큼 스크롤을 움직여주기 위해서이다.

     

    즉, walk는 'startX로부터 마우스를 얼만큼 움직였는지'를 의미하는 변수가 된다.

     

    이제 위의 코드를 실행해보자.

    뭔가 이상하게 동작한다는 것을 알 수 있다.

    매번 클릭할 때마다 스크롤의 위치가 초기화되고, 스크롤되는 방향도 우리가 원하는 방향과 반대로 움직인다.

    스크롤되는 방향이 마우스 방향과 함께 움직이는 건, walk 변수가 양수이기 때문에 당연하다고 치더라도

    매번 클릭할 때마다 스크롤의 위치가 초기화되는 건 왜 그런걸까?

     

    그건 바로, slider.scrollLeft를 walk로 지정해줬기 때문이다.

    walk는 위에서도 언급했듯이, 의미를 해석하자면

    '마우스를 처음 클릭한 지점(startX)으로부터 mousemove를 통해 움직인 거리'에 해당하는데,

    마우스를 매번 클릭할 때마다 마우스를 처음 클릭한 지점이 매번 새로 계산되기 때문에, 

    '마우스를 움직인 거리' 또한 당연히 매번 0으로 초기화 되어버리는 것이다.

     

    그래서 마우스를 새로 클릭할 때마다 slider.scrollLeft가 0이 되기 때문에

    스크롤도 맨 좌측으로 계속해서 땡겨지게 되는 것이다.

     

    이를 해결하는 방법은 간단하다.

    맨 처음 스크롤 위치에서 walk 만큼 뺀 값을 현재의 slider.scrollLeft로 지정해주면 된다.

    slider.addEventListener('mousemove', e => {
      if (!isDown) return;
      e.preventDefault();
      const x = e.pageX - slider.offsetLeft;
      const walk = x - startX;
      slider.scrollLeft = scrollLeft - walk;
    });

    이렇게 하면, 맨 처음에는 scrollLeft가 0이기 때문에 양수를 음수로 바꿔주는 효과밖에 없지만

    일정 영역만큼 드래그를 하고 나면, 드래그를 한 만큼 이동하고 난 스크롤의 위치가 scrollLeft 변수에 저장된다.

    그래서 다시 마우스를 클릭하더라도, scrollLeft에서 시작하기 때문에 스크롤의 위치가 초기화되지 않는 것.

    전체 자바스크립트 코드

    <script>
      const slider = document.querySelector('.items');
      let isDown = false;
      let startX;
      let scrollLeft;
    
      slider.addEventListener('mousedown', e => {
        isDown = true;
        slider.classList.add('active');
        startX = e.pageX - slider.offsetLeft;
        scrollLeft = slider.scrollLeft;
      });
    
      slider.addEventListener('mouseleave', () => {
        isDown = false;
        slider.classList.remove('active');
      });
    
      slider.addEventListener('mouseup', () => {
        isDown = false;
        slider.classList.remove('active');
      });
    
      slider.addEventListener('mousemove', e => {
        if (!isDown) return; 
        e.preventDefault();
        const x = e.pageX - slider.offsetLeft;
        const walk = x - startX;
        slider.scrollLeft = scrollLeft - walk;
      });
    </script>

     

    참고

     

    Element.scrollLeft - Web APIs | MDN

    The Element.scrollLeft property gets or sets the number of pixels that an element's content is scrolled from its left edge. If the element's direction is rtl (right-to-left), then scrollLeft is 0 when the scrollbar is at its rightmost position (at the star

    developer.mozilla.org

     

    MouseEvent.clientX - Web API | MDN

    {{{APIRef("DOM 이벤트")}} MouseEvent} 인터페이스의 clientX 읽기 전용 속성은 이벤트가 발생한 애플리케이션 viewport}} 내에 수평 좌표를 제공한다(페이지 내의 좌표와는 반대). 예를 들어 뷰포트의 왼쪽

    developer.mozilla.org

     

    MouseEvent.pageX - Web APIs | MDN

    The pageX read-only property of the MouseEvent interface returns the X (horizontal) coordinate (in pixels) at which the mouse was clicked, relative to the left edge of the entire document. This includes any portion of the document not currently visible. B

    developer.mozilla.org

     

    clientX, offsetX, pageX, screenX의 차이

    1. clientX, clientY 위 메서드는 클라이언트 영역 내의 가로,세로 좌표를 제공합니다. 여기서 클라이언트 영역은 현재 보이는 브라우저 화면이 기준이 됩니다. clientX : 브라우저 페이지에서의 X좌표

    megaton111.cafe24.com

     

    반응형

    'Web > JS30' 카테고리의 다른 글

    Countdown Timer ⏱  (0) 2021.01.02
    Video Speed Controller UI  (0) 2020.12.27
    Stripe Follow Along Nav  (0) 2020.12.26
    Event Capture, Propagation, Bubbling and Once  (0) 2020.12.24
    Sticky Nav 🩹  (0) 2020.12.23

    댓글

Designed by Tistory.