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

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

Left, Right로 나뉜 split 구조에 적용

HTML

<div class="container">
	<div class="left">Left</div>
    <div class="resizer" id="dragMe"></div>
    <div class="right">Right</div>
</div>

CSS

.container {
  display: flex;

  /* 영역 구분을 위해 선을 설정 */
  border: 1px solid #cbd5e0;
  height: 16rem;
  width: 100%;
}
.left {
  /* 초기 크기를 절반으로 설정 */
  width: 50%;

  /* 중앙 정렬 */
  align-items: center;
  display: flex;
  justify-content: center;
}
.resizer {
  background-color: #cbd5e0;
  cursor: ew-resize;
  height: 100%;
  width: 2px;
}
.right {
  /* left가 차지하고 남은 공간에 따라 유동적으로 변화 */
  flex: 1;

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

JavaScript

// 대상 Element 선택
const resizer = document.getElementById('dragMe');
const leftSide = resizer.previousElementSibling;
const rightSide = resizer.nextElementSibling;

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

// 크기 조절시 왼쪽 Element를 기준으로 삼기 위해 선언
let leftWidth = 0;

// resizer에 마우스 이벤트가 발생하면 실행하는 Handler
const mouseDownHandler = function (e) {
    // 마우스 위치값을 가져와 x, y에 할당
    x = e.clientX;
    y = e.clientY;
    // left Element에 Viewport 상 width 값을 가져와 넣음
    leftWidth = leftSide.getBoundingClientRect().width;

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

const mouseMoveHandler = function (e) {
    // 마우스가 움직이면 기존 초기 마우스 위치에서 현재 위치값과의 차이를 계산
    const dx = e.clientX - x;
    const dy = e.clientY - y;

  	// 크기 조절 중 마우스 커서를 변경함
    // class="resizer"에 적용하면 위치가 변경되면서 커서가 해제되기 때문에 body에 적용
    document.body.style.cursor = 'col-resize';
    
    // 이동 중 양쪽 영역(왼쪽, 오른쪽)에서 마우스 이벤트와 텍스트 선택을 방지하기 위해 추가
    leftSide.style.userSelect = 'none';
    leftSide.style.pointerEvents = 'none';
    
    rightSide.style.userSelect = 'none';
    rightSide.style.pointerEvents = 'none';
    
    // 초기 width 값과 마우스 드래그 거리를 더한 뒤 상위요소(container)의 너비를 이용해 퍼센티지를 구함
    // 계산된 퍼센티지는 새롭게 left의 width로 적용
    const newLeftWidth = ((leftWidth + dx) * 100) / resizer.parentNode.getBoundingClientRect().width;
    leftSide.style.width = `${newLeftWidth}%`;
};

const mouseUpHandler = function () {
    // 모든 커서 관련 사항은 마우스 이동이 끝나면 제거됨
    resizer.style.removeProperty('cursor');
    document.body.style.removeProperty('cursor');

    leftSide.style.removeProperty('user-select');
    leftSide.style.removeProperty('pointer-events');

    rightSide.style.removeProperty('user-select');
    rightSide.style.removeProperty('pointer-events');

    // 등록한 마우스 이벤트를 제거
    document.removeEventListener('mousemove', mouseMoveHandler);
    document.removeEventListener('mouseup', mouseUpHandler);
};

// 마우스 down 이벤트를 등록
resizer.addEventListener('mousedown', mouseDownHandler);

Right에 추가로 Top, Bottom으로 나뉜 split 구조에 적용

HTML

<div class="container">
  <div class="left">Left</div>
  <div class="resizer" data-direction="horizontal"></div>
  <div class="right">
    <div class="top">Top</div>
    <div class="resizer" data-direction="vertical"></div>
    <div class="bottom">Bottom</div>
  </div>
</div>

CSS

.container {
  display: flex;

  /* 영역 구분을 위해 선을 설정 */
  border: 1px solid #cbd5e0;
  height: 32rem;
  width: 100%;
}
.resizer[data-direction="horizontal"] {
  background-color: #cbd5e0;
  cursor: ew-resize;
  height: 100%;
  width: 2px;
}
.resizer[data-direction="vertical"] {
  background-color: #cbd5e0;
  cursor: ns-resize;
  height: 2px;
  width: 100%;
}
.left {
  /* 초기 크기를 1/4으로 설정 */
  width: 25%;

  /* 중앙 정렬 */
  align-items: center;
  display: flex;
  justify-content: center;
}
.right {
  /* left가 차지하고 남은 공간에 따라 유동적으로 변화 */
  flex: 1;

  /* 중앙 정렬 및 하위 요소 배치를 column 식으로 내려가듯이 배치*/
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.top {
  /* 초기 높이값 지정 */
  height: 12rem;

  /* 중앙 정렬 */
  align-items: center;
  display: flex;
  justify-content: center;
}
.bottom {
  /* top이 차지하고 남은 공간에 따라 유동적으로 변화 */
  flex: 1;

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

JavaScript

import "./styles.css";
const resizable = function (resizer) {
  const direction = resizer.getAttribute("data-direction") || "horizontal";
  const prevSibling = resizer.previousElementSibling;
  const nextSibling = resizer.nextElementSibling;

  //  마우스의 위치값 저장을 위해 선언
  let x = 0;
  let y = 0;
  let prevSiblingHeight = 0;
  let prevSiblingWidth = 0;

  // resizer에 마우스 이벤트가 발생하면 실행하는 Handler
  const mouseDownHandler = function (e) {
    // 마우스 위치값을 가져와 x, y에 할당
    x = e.clientX;
    y = e.clientY;
    // 대상 Element에 위치 정보를 가져옴
    const rect = prevSibling.getBoundingClientRect();
    // 기존 높이와 너비를 각각 할당함
    prevSiblingHeight = rect.height;
    prevSiblingWidth = rect.width;

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

  const mouseMoveHandler = function (e) {
    // 마우스가 움직이면 기존 초기 마우스 위치에서 현재 위치값과의 차이를 계산
    const dx = e.clientX - x;
    const dy = e.clientY - y;

    // 이동 방향에 따라서 별도 동작
    // 기본 동작은 동일하게 기존 크기에 마우스 드래그 거리를 더한 뒤 상위요소(container)를 이용해 퍼센티지를 구함
    // 계산 대상이 x 또는 y인지에 차이가 있음
    switch (direction) {
      case "vertical":
        const h =
          ((prevSiblingHeight + dy) * 100) /
          resizer.parentNode.getBoundingClientRect().height;
        prevSibling.style.height = `${h}%`;
        break;
      case "horizontal":
      default:
        const w =
          ((prevSiblingWidth + dx) * 100) /
          resizer.parentNode.getBoundingClientRect().width;
        prevSibling.style.width = `${w}%`;
        break;
    }

    // 크기 조절 중 마우스 커서를 변경함
    // class="resizer"에 적용하면 위치가 변경되면서 커서가 해제되기 때문에 body에 적용
    const cursor = direction === "horizontal" ? "col-resize" : "row-resize";
    resizer.style.cursor = cursor;
    document.body.style.cursor = cursor;

    prevSibling.style.userSelect = "none";
    prevSibling.style.pointerEvents = "none";

    nextSibling.style.userSelect = "none";
    nextSibling.style.pointerEvents = "none";
  };

  const mouseUpHandler = function () {
    // 모든 커서 관련 사항은 마우스 이동이 끝나면 제거됨
    resizer.style.removeProperty("cursor");
    document.body.style.removeProperty("cursor");

    prevSibling.style.removeProperty("user-select");
    prevSibling.style.removeProperty("pointer-events");

    nextSibling.style.removeProperty("user-select");
    nextSibling.style.removeProperty("pointer-events");

    // 등록한 마우스 이벤트를 제거
    document.removeEventListener("mousemove", mouseMoveHandler);
    document.removeEventListener("mouseup", mouseUpHandler);
  };

  // 마우스 down 이벤트를 등록
  resizer.addEventListener("mousedown", mouseDownHandler);
};

// 모든 resizer에 만들어진 resizable을 적용
document.querySelectorAll(".resizer").forEach(function (ele) {
  resizable(ele);
});

출처

profile
달콤살벌
post-custom-banner

0개의 댓글