JavaScript

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

·7 min read

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)

!codesandbox[resizable-split-views-left-right-lkiqc8?fontsize=14&hidenavigation=1&theme=dark]

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)
})

!codesandbox[resizable-split-views-left-right-top-bottom-nkxdo1?fontsize=14&hidenavigation=1&theme=dark]

출처

← Previous
ThreeJs+React로 마인크래프트 만들기 번역(by Daniel Bark from freeCodeCamp) - TextureSelector, World
Next →
javascript를 이용해 mouse로 html 요소 drag하기(코드 저장용)