javascript를 이용해 mouse로 html 요소 drag하기(코드 저장용)

설탕유령·2022년 11월 3일
1
post-custom-banner

저번에 htmldom을 참조해 element의 마우스를 통한 크기 조절을 진행했다.
이번에는 위치에 대한 이벤트가 필요해 정리하기로 했다.

마우스로 drag 하기

HTML

<div class="container"> 
  <div class="draggable" id="dragMe">Drag me</div>
</div>

CSS

.container {
  /* 중앙 정렬 */
  align-items: center;
  display: flex;
  justify-content: center;

  /* 높이 지정 */
  min-height: 32rem;
}
.draggable {
  /* 커서를 이동형식으로 주고, 위치를 절대형식으로 지정 */
  cursor: move;
  position: absolute;
  user-select: none;

  /* 중앙 정렬 */
  align-items: center;
  display: flex;
  justify-content: center;

  /* 디자인 */
  border: 1px solid #cbd5e0;
  height: 8rem;
  width: 8rem;
}

JavaScript


document.addEventListener('DOMContentLoaded', function () {
  // 마우스의 위치값 저장
  let x = 0;
  let y = 0;

  // 대상 Element 가져옴
  const ele = document.getElementById('dragMe');

  // 마우스 누른 순간 이벤트
  const mouseDownHandler = function (e) {
    // 누른 마우스 위치값을 가져와 저장
    x = e.clientX;
    y = e.clientY;

    // 마우스 이동 및 해제 이벤트를 등록
    document.addEventListener('mousemove', mouseMoveHandler);
    document.addEventListener('mouseup', mouseUpHandler);
  };

  const mouseMoveHandler = function (e) {
    // 마우스 이동시 초기 위치와의 거리차 계산
    const dx = e.clientX - x;
    const dy = e.clientY - y;

    // 마우스 이동 거리 만큼 Element의 top, left 위치값에 반영
    ele.style.top = `${ele.offsetTop + dy}px`;
    ele.style.left = `${ele.offsetLeft + dx}px`;

    // 기준 위치 값을 현재 마우스 위치로 update
    x = e.clientX;
    y = e.clientY;
  };

  const mouseUpHandler = function () {
    // 마우스가 해제되면 이벤트도 같이 해제
    document.removeEventListener('mousemove', mouseMoveHandler);
    document.removeEventListener('mouseup', mouseUpHandler);
  };

  ele.addEventListener('mousedown', mouseDownHandler);
});

출처

drag 영역 나누기

단순히 위치만 옮겨다니는 Drag 대신 영역을 지정해 붙는 형식이 필요하다
HTML

<div style="align-items: center; display: flex; justify-content: center; padding: 4rem 0">
  <div id="list">
    <div class="draggable">A</div>
    <div class="draggable">B</div>
    <div class="draggable">C</div>
    <div class="draggable">D</div>
    <div class="draggable">E</div>
  </div>
</div>

CSS

.draggable {
  /* 커서를 이동형식으로 줌*/
  cursor: move;
  margin-bottom: 1rem;
  user-select: none;

  /* 중앙 정렬 */
  align-items: center;
  display: flex;
  justify-content: center;

  /* 크기 지정 */
  height: 4rem;
  width: 16rem;

  /* Misc */
  border: 1px solid #cbd5e0;
}
.placeholder {
  background-color: #edf2f7;
  border: 2px dashed #cbd5e0;
  margin-bottom: 1rem;
}

JavaScript


document.addEventListener('DOMContentLoaded', function () {
  // list div element를 가져옴
  const list = document.getElementById('list');

  let draggingEle;
  let placeholder;
  let isDraggingStarted = false;

  // 마우스 위치값을 위해 선언
  let x = 0;
  let y = 0;

  // 노드의 위치를 바꾸는 함수
  const swap = function (nodeA, nodeB) {
    // 상위 컨테이너 요소를 가져옴
    const parentA = nodeA.parentNode;
    // nodeA에 형제(다음 요소)가 nodeB라면 nodeA, 아닌 경우 다음 요소를 담음
    // sibling에서 조건처리하는 이유는 placeholder와 drag element의 위치를 조정하기 위해서
    const siblingA = nodeA.nextSibling === nodeB ? nodeA : nodeA.nextSibling;

    // nodeA를 nodeB의 뒤로 삽입
    nodeB.parentNode.insertBefore(nodeA, nodeB);

    // nobeB를 nodeA에 형제(다음요소) 뒤에 넣음
    parentA.insertBefore(nodeB, siblingA);
  };

  // nodeA가 nodeB 위에 존재하는지 확인하는 함수
  const isAbove = function (nodeA, nodeB) {
    // 각 node의 위치정보를 가져옴
    const rectA = nodeA.getBoundingClientRect();
    const rectB = nodeB.getBoundingClientRect();

    // top을 기준으로 A가 B보다 작으면 화면 위치상 A는 B보다 상위에 존재함으로 above가 성립
    return rectA.top + rectA.height / 2 < rectB.top + rectB.height / 2;
  };

  const mouseDownHandler = function (e) {
    // 현재 이벤트가 발생한 element를 저장
    draggingEle = e.target;

    // 선택된 element의 위치를 중심으로 마우스 클릭 위치를 계산
    const rect = draggingEle.getBoundingClientRect();
    x = e.pageX - rect.left;
    y = e.pageY - rect.top;

    // mouse 움직임, 해제 이벤트 적용
    document.addEventListener('mousemove', mouseMoveHandler);
    document.addEventListener('mouseup', mouseUpHandler);
  };

  const mouseMoveHandler = function (e) {
    // 현재 이벤트가 발생한 element의 위치 정보 가져옴
    const draggingRect = draggingEle.getBoundingClientRect();

    // 첫 움직임인 경우 적용
    if (!isDraggingStarted) {
      isDraggingStarted = true;

      // 선택한 element가 움직이는 동안 표시할 placeholder element를 생성
      placeholder = document.createElement('div');
      placeholder.classList.add('placeholder');
      draggingEle.parentNode.insertBefore(placeholder, draggingEle.nextSibling);
      placeholder.style.height = `${draggingRect.height}px`;
    }

    // 드래그 하는 동안 element가 마우스 위치를 따라가도록 설정
    draggingEle.style.position = 'absolute';
    draggingEle.style.top = `${e.pageY - y}px`;
    draggingEle.style.left = `${e.pageX - x}px`;

    // 선택 element에 전 element와 다음 element를 선택함
    // placeholder가 들어가 있기 때문에 순서는 다음과 같음
    // 이전 element
    // drag 중인 element
    // placeholder element
    // 다음 element
    const prevEle = draggingEle.previousElementSibling;
    const nextEle = placeholder.nextElementSibling;

    // 이전 element가 존재하고 drag 중인 element가 이전 element 위에 존재하는 경우
    if (prevEle && isAbove(draggingEle, prevEle)) {
      // 서로의 위치를 바꿈
      // swap을 통해 place와 drag를 바꾸는 이유는 한번 움직이는게 아닌 연속적으로 상위요소로 올라갈 때, preEle 처리전 place와 drag의 위치를 정리하기 위해서임
      swap(placeholder, draggingEle);
      swap(placeholder, prevEle);
      return;
    }

    // 다음 element가 존재하고 drag 중인 element가 다음 element 위에 존재하는 경우
    if (nextEle && isAbove(nextEle, draggingEle)) {
      swap(nextEle, placeholder);
      swap(nextEle, draggingEle);
    }
  };

  const mouseUpHandler = function () {
    // placeholder가 존재하면 지워줌
    placeholder && placeholder.parentNode.removeChild(placeholder);

    // absolute로 움직이던 속성을 다 제거
    draggingEle.style.removeProperty('top');
    draggingEle.style.removeProperty('left');
    draggingEle.style.removeProperty('position');

    // 위치와 element 관련 date를 초기화
    x = null;
    y = null;
    draggingEle = null;
    isDraggingStarted = false;

    // 이벤트 해제
    document.removeEventListener('mousemove', mouseMoveHandler);
    document.removeEventListener('mouseup', mouseUpHandler);
  };

  // 모든 draggable 요소에 mouseDown 이벤트 적용
  [].slice.call(list.querySelectorAll('.draggable')).forEach(function (item) {
    item.addEventListener('mousedown', mouseDownHandler);
  });
});

profile
달콤살벌
post-custom-banner

0개의 댓글