# mouse-drag-and-drop
모던 HTML 표준에서는 드래그 앤 드롭 내용이 있습니다.
dragstart
dragend
이벤트- 운영체제의 파일 관리 애플리케이션으로 부터 파일을 드래그하여 브라우저 화면에 드롭하는 기능을 제공함.
- 한계
- 특정 영역에서 드래그 하는 것을 막을 수 없음
- 수평 / 수직으로만 드래그 할 수 없다.
- 모바일 환경 지원 부족
- 한계를 극복하기 위해 마우스 이벤트를 이용하여 drag&drop 기능을 구현해봅니다.
DragZone, Droppable, Draggable 및 기타 클래스 등 아키텍처를 구축하는 프레임워크가 있습니다. 대부분은 앞서 드래그와 드롭에 대한 설명과 유사한 작업을 하므로 이해하기 쉽습니다. 때로는 제3의 솔루션 적용보다 쉽게 수행할 수 있습니다.
# drag & drop 알고리즘
mousedown
- 움직임이 필요한 요소를 준비함.
mousemove
position: absolute
의left, top
을 변경
mouseup
- 드래그 앤 드롭 완료와 관련된 모든 작업 수행
# 공을 드래그 하는 구현
- HTMLElement.offsetWidth
- MouseEvent.pageX
- 전체 문서의 왼쪽 가장자리를 기준으로 마우스를 클릭 한 지점의 X (수평) 좌표 (픽셀)를 반환합니다
<html>
<img id="ball" src="https://avatars.githubusercontent.com/u/31977543?s=88&u=abc3f0443f4c82f349f3103cd9010f272db55fe6&v=4"/>
</html>
<script>
ball.onmousedown = (event) => {
ball.style.position = 'absolute';
ball.style.zIndex = 1000;
// 현재 위치한 부모에서 body 로 직접 이동하여 body 기준으로 위치를 지정함
document.body.append(ball);
// 공을 pageX, pageY 좌표 중앙에 위치하게 합니다.
function moveAt(pageX, pageY) {
ball.style.left = pageX - ball.offsetWidth / 2 + 'px';
ball.style.top = pageY - ball.offsetHeight / 2 + 'px';
}
// 포인터 아래로 공을 이동시킨다.
moveAt(event.pageX, event.pageY);
function onMouseMove(event) {
moveAt(event.pageX, event.pageY);
}
document.addEventListener('mousemove', onMouseMove);
ball.onmouseup = () => {
document.removeEventListener('mousemove', onMouseMove);
ball.onmouseup = null;
}
ball.ondragstart = function() {
return false;
};
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
브라우저 자체적으로 이미지나 요소에 대한 드래그 앤 드롭을 지원한다면.
- drag 를 할 때, 복사된 공을 드래그 하는 동작이 나타날 수 있다.
- 이럴 때 다음 코드를 추가해 준다.
ball.ondragstart = function() {
return false;
};
1
2
3
2
3
이미지 내의 어떤 좌표를 클릭하면 이미지가 움직여버리는 문제점이 있습니다.
function moveAt(pageX, pageY) {
ball.style.left = pageX - ball.offsetWidth / 2 + 'px';
ball.style.top = pageY - ball.offsetHeight / 2 + 'px';
}
1
2
3
4
2
3
4
-
- 이벤트가 발생한 애플리케이션 viewport 내에 수평 좌표를 제공한다
Element.getBoundingClientRect()
- DOMRect 요소의 크기 및 뷰포트를 기준으로 한 위치에 대한 정보를 제공 하는 객체를 반환합니다.
-
- 직사각형의 크기와 위치를 나타냅니다.
ball.onmousedown = (event) => {
// pointer 에서 공의 왼쪽 위 모서리까지의 거리를 저장합니다.
// 공을 드래그 하는 동안 이 거리를 유지한다.
const shiftX = event.clientX - ball.getBoundingClientRect().left;
const shiftY = event.clientY - ball.getBoundingClientRect().top;
ball.style.position = 'absolute';
ball.style.zIndex = 1000;
document.body.append(ball);
// 초기 이동을 고려한 좌표 (pageX, pageY)에서
// 공을 이동합니다.
function moveAt(pageX, pageY) {
ball.style.left = pageX - shiftX + 'px';
ball.style.top = pageY - shiftY + 'px';
}
// 포인터 아래로 공을 이동시킨다.
moveAt(event.pageX, event.pageY);
function onMouseMove(event) {
moveAt(event.pageX, event.pageY);
}
document.addEventListener('mousemove', onMouseMove);
ball.onmouseup = () => {
document.removeEventListener('mousemove', onMouseMove);
ball.onmouseup = null;
}
}
ball.ondragstart = function() {
return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 잠재적 드롭 대상
# 마우스 이벤트의 맨 위 요소에서만 이벤트가 발생하는 문제
blue
에서는 이벤트 핸들러가 동작하지 않습니다. red 가 만약 다른 곳으로 이동하게 된다면 blue 가
디스플레이에 보이게 되고, blue 의 이벤트 핸들러가 동작하는 것을 기대해야 합니다.
<style>
div {
width: 50px;
height: 50px;
position: absolute;
top: 0;
}
</style>
<div style="background:blue" onmouseover="alert('never works')"></div>
<div style="background:red" onmouseover="alert('over red!')"></div>
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
document.elementFromPoint
- 윈도우 기준 좌표에서 가장 많이 중첩된 요소를 반환한다.
다음과 같이 구현 합니다.
<html>
<img src="https://en.js.cx/clipart/soccer-gate.svg" id="gate" class="droppable">
<img src="https://en.js.cx/clipart/ball.svg" id="ball">
</html>
<script>
// 잠재적 드롭 가능한 요소
let currentDroppable = null;
ball.onmousedown = function (event) {
let shiftX = event.clientX - ball.getBoundingClientRect().left;
let shiftY = event.clientY - ball.getBoundingClientRect().top;
ball.style.position = 'absolute';
ball.style.zIndex = 1000;
document.body.append(ball);
moveAt(event.pageX, event.pageY);
function moveAt(pageX, pageY) {
ball.style.left = pageX - shiftX + 'px';
ball.style.top = pageY - shiftY + 'px';
}
function onMouseMove(event) {
moveAt(event.pageX, event.pageY);
ball.hidden = true;
// elemBelow : 드롭할 수 있는 공의 아래 요소, 축구골대
const elemBelow = document.elementFromPoint(event.clientX, event.clientY);
ball.hidden = false;
// 공을 윈도우 밖으로 드래그 했을 때,
// clientX, clientY 가 윈도우 밖에 있으면, elementFromPoint 는 null 반환
if (!elemBelow) return;
// 잠재적으로 드롭할 수 있는 요소를 'droppable' 클래스로 지정한다. 축구 골대
const droppableBelow = elemBelow.closest('.droppable');
// 둘다 null 일 수 있다. 들어오거나, 나가거나에 해당할 때,
// currentDroppale = null : 축구 골대 밖에 있을 때,
// droppableBelow = null : 이벤트 동안 축구 골대 안에 없을 때,
if (currentDroppable !== droppableBelow) {
if (currentDroppable) {
// 축구 골대 밖에 있을 때 강조 제거
leaveDroppable(currentDroppable);
}
currentDroppable = droppableBelow;
if (currentDroppable) {
// 축구 골대 안으로 들어오는 것을 처리하는 로직
enterDroppable(currentDroppable);
}
}
document.addEventListener('mousemove', onMouseMove);
ball.onmouseup = function() {
document.removeEventListener('mousemove', onMouseMove);
ball.onmouseup = null;
};
}
function enterDroppable(elem) {
elem.style.background = 'pink';
}
function leaveDroppable(elem) {
elem.style.background = '';
}
ball.ondragstart = function() {
return false;
};
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# 예시 2
<html>
<body>
<div id="slider" class="slider">
<div class="thumb"></div>
</div>
<script>
const thumb = slider.querySelector('.thumb');
thumb.onmousedown = function(event) {
event.preventDefault(); // prevent selection start (browser action)
let shiftX = event.clientX - thumb.getBoundingClientRect().left;
// shiftY not needed, the thumb moves only horizontally
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
function onMouseMove(event) {
let newLeft = event.clientX - shiftX - slider.getBoundingClientRect().left;
// the pointer is out of slider => lock the thumb within the bounaries
if (newLeft < 0) {
newLeft = 0;
}
let rightEdge = slider.offsetWidth - thumb.offsetWidth;
if (newLeft > rightEdge) {
newLeft = rightEdge;
}
thumb.style.left = newLeft + 'px';
}
function onMouseUp() {
document.removeEventListener('mouseup', onMouseUp);
document.removeEventListener('mousemove', onMouseMove);
}
};
thumb.ondragstart = function() {
return false;
};
</script>
</body>
</html>
<style>
.slider {
border-radius: 5px;
background: #E0E0E0;
background: linear-gradient(left top, #E0E0E0, #EEEEEE);
width: 310px;
height: 15px;
margin: 5px;
}
.thumb {
width: 10px;
height: 25px;
border-radius: 3px;
position: relative;
left: 10px;
top: -5px;
background: blue;
cursor: pointer;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# Reference
https://ko.javascript.info/mouse-drag-and-drop#ref-246
← 마우스 이벤트 Pointer 이벤트 →