저번에 htmldom을 참조해 element의 마우스를 통한 크기 조절을 진행했다.
이번에는 위치에 대한 이벤트가 필요해 정리하기로 했다.
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 대신 영역을 지정해 붙는 형식이 필요하다
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);
});
});