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