0. 서론
최근에 HTML, CSS, Vanilla Javascript를 이용하여 메모장을 만들었다. 생성된 메모들을 화면에 보여줄 때 단순하게 나열만 해주다니 심심하게 느껴졌다. 그래서, Windows의 Sticky Notes처럼 드래그앤드롭으로 위치를 자유롭게 설정할 수 있도록 기능을 추가해보았는데, 다른데서도 유용하게 사용할 수 있을것 같아서 정리를 해보려고한다.
그럼 예제를 통해 드래그 앤 드롭 기능을 구현해보도록 하자.
1. 프로젝트 준비
1-1. Chrome Browser
1) IE와 같은 구형브라우저에서는 ES를 적용하려면 BABEL을 이용해야하기 때문에 Chrome Browser를 사용.
2) 다운로드
- https://www.google.com/intl/ko/chrome/
1-2. 프로젝트 구성
1) [ css/common.css ] 파일
: 전반적인 style을 작성할 css파일.
2) [ js/common.js ] 파일
: Element에 이벤트를 적용하는 js파일.
: DOM이 모두 로드된 후 적용할 로직을 이 곳에 작성.
3) [ js/event_handler.js ] 파일
: Elemet에 적용할 Event Handler 함수들을 작성하는 js파일.
4) [ index.html ] 파일
: 메인 HTML파일
2. css/common.css
2-1. 스크립트 작성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
/* css/common.js */
/* 도화지 크기 설정 */
html, body { padding: 0; margin: 0; width: 100%; height: 100%; }
/* 공이 자유자재로 움직일 수 있도록 position을 'absolute'로 설정 */
.ball { position: absolute; }
/* 공을 선택했을 때, 그림자가 생기도록 설정 */
.ball:hover { box-shadow: 0 0 10px 0 darkgray; cursor: pointer; }
/* 공의 색상 설정 */
.green { background-color: lightgreen; }
.blue { background-color: lightblue; }
.pink { background-color: lightpink }
/* 공의 크기 설정 */
.s200 { width: 200px; height: 200px; border-radius: 200px; }
.s100 { width: 100px; height: 100px; border-radius: 100px; }
.s50 { width: 50px; height: 50px; border-radius: 50px; }
|
cs |
1) [ 7 ln ] - Element의 position을 절대값으로 설정
: absolute로 설정하면 top, left 등에 의해 위치를 조절할 수 있음.
3. js/event_handler.js
3-1. 스크립트 작성
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
73
74
75
76
77
78
|
// js/event_handler.js
// 공 선택 이벤트 핸들러
function handleMouseDown(event){
event.preventDefault();;
const balls = document.querySelectorAll(".ball");
const el = event.target;
const classList = el.classList;
if( !classList.contains("hold") ){
// 공을 클릭했을 때, 마우스 커서의 XY좌표
const mouseX = event.clientX;
const mouseY = event.clientY;
// 선택한 공의 XY좌표 (왼쪽 상단 모서리 기준)
const ballPos = el.getBoundingClientRect();
const ballX = ballPos.x;
const ballY = ballPos.y;
// 선택한 공 안에 있는 마우스 커서의 XY좌표
const gapX = mouseX - ballX;
const gapY = mouseY - ballY;
el.setAttribute("gap-x", gapX);
el.setAttribute("gap-y", gapY);
// 선택한 공을 맨 앞으로 가지고 오기
const maxPriority = (
balls.length > 0
? Math.max.apply(null, Array.from(balls).map(ball=>ball.getAttribute("priority")))
: 9999
) + 1;
el.setAttribute("priority", maxPriority);
el.style["z-index"] = maxPriority;
// 선택한 공에 'hold' class를 추가
classList.add("hold");
}
}
// 공 움직임 이벤트 핸들러
function handleMouseMove(event){
event.preventDefault();
const el = document.querySelector(".ball.hold");
if( el ){
// 움직이는 마우스 커서의 XY좌표
const mouseX = event.clientX;
const mouseY = event.clientY;
// 선택한 공 안에 있는 마우스 커서의 XY좌표
const gapX = el.getAttribute("gap-x");
const gapY = el.getAttribute("gap-y");
// 마우스 커서의 위치에 따른 공의 XY좌표
const ballX = mouseX - gapX;
const ballY = mouseY - gapY;
// 공의 위치를 변경
el.style.left = ballX+"px";
el.style.top = ballY+"px";
}
}
// 공 놓기 이벤트 핸들러
function handleMouseUp(event){
event.preventDefault();
const el = document.querySelector(".ball.hold");
if( el ){
// 움직이면 적용된 속성 및 class를 삭제
el.removeAttribute("gap-x")
el.removeAttribute("gap-y")
el.classList.remove("hold");
}
}
|
cs |
1) [ 29-35 ln ] - 선택한 공을 맨 앞으로 가지고 오기
: 공을 클릭했을 때, z-index의 최대값에 +1을 더해서 가장 앞에 위치하도록 설정.
2) [ 53-58 ln ] - 마우스가 움직일 때 공의 위치 계산
: 원래는 마우스의 커서가 ballX, ballY 좌표에 위치하게 됨.
: 공을 움직일 때, 처음 클릭했던 위치를 고정하려면, 공의 위치와 마우스 위치의 차이값(gapX, gapY)을 구해야함.
: 마우스가 이동할 때마다 마우스의 위치에서 해당 값을 빼줌으로써 ballX, ballY의 좌표를 구할 수 있음.
4. js/common.js
4-1. 스크립트 작성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
// js/common.js
const balls = document.querySelectorAll(".ball");
balls.forEach(function(ball, idx){
// 공의 우선순위 설정
let priority = ball.getAttribute("priority");
if( !priority ){
priority = idx+1;
ball.setAttribute("priority", priority);
}
ball.style["z-index"] = priority;
// 공 선택 이벤트 바인딩
ball.addEventListener('mousedown', handleMouseDown);
});
// 마우스 이벤트 바인딩
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
|
cs |
1) [ 7-12 ln ] - 공의 우선순위(인덱스) 설정
: 우선순위(priority) 속성이 없으면 index 값으로 자동 부여됨.
2) [ 15 ln ] - 공을 선택하는 이벤트를 ball Element에 바인딩
3) [ 19-20 ln ] - 마우스의 이동과 선택 해제 이벤트를 document에 바인딩
: ball Element에 바인딩 할 경우, 마우스 커서를 빠르게 움직이여서 공을 벗어나면 제어가 안되기 때문.
5. index.html
5-1. 스크립트 작성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<link href="css/common.css" rel="stylesheet"/>
<script src="js/event_handler.js"></script>
</head>
<body>
<div class="ball green s200"></div>
<div class="ball blue s100"></div>
<div class="ball pink s50"></div>
<script src="js/common.js"></script>
</body>
</html>
|
cs |
1) [ 5~6 ln ] - css와 event_handler.js를 로드
2) [ 9~11 ln ] - 공을 여러개 생성
3) [ 12 ln ] - common.js를 로드
: <body>의 하단에 javascript를 로드하는 것은 DOM이 다 로드된 후 적용하기 위함.
: <head>에서 로드할 경우, <div>가 생성되지 않았기 때문에 이벤트가 적용되지 않음.
6. 실행
6-1. index.html을 실행
1) 파일을 더블클릭하여 실행
2) 파일을 Chrome Browser로 드로그앤드롭하여 실행
3) 주소창에 파일의 위치를 입력하여 실행
: file:///C:/work/javascript/ball/index.html
6-2. 실행 결과
1) 공을 선택하면 선택된 공에 그림자가 생기며, 맨 앞으로 이동됨.
2) 공을 움직여보면 사진과 같이 자유자재로 움직일 수 있음.
마치며
- 드로그앤드롭 기능은 구현해봐야겠다고 생각만하고 미루고 있었는데, 직접 만들어보니 생각보다 간단했다. 다만 Browser의 호환성은 따지지 않고 만들었기 때문에 Chrome에서만 잘 돌아갈 것이다.
- 다음에는 이 기능을 이용해서 재밌는 예제를 만들어보고 포스팅해보도록 하겠다.
- 2020/03/03 - [Font-end/Javascript] - [그림판] HTML, CSS, JS로 그림판 만들기
'Font-end > Javascript' 카테고리의 다른 글
[Context Menu] 나만의 컨텍스트 메뉴 만들기 (1) | 2020.05.29 |
---|---|
[Redux] 무작정 시작하기 (1) - Redux란? (0) | 2020.04.23 |
[모듈] Prototype을 이용한 모듈 패턴으로 Element 모듈 만들기 (0) | 2020.03.19 |
[공적마스크API] 공적 마스크 판매 정보 조회 (2) | 2020.03.18 |
[그림판] HTML, CSS, JS로 그림판 만들기 (1) | 2020.03.03 |
댓글