[JS] 드래그 앤 드롭으로 엘리먼트를 특정 위치에 배치하기 #1

[JS] 드래그 앤 드롭으로 엘리먼트를 특정 위치에 배치하기 #1

TL;DR

  • 드래그할 요소Draggable로 설정할 DOM Element에 draggable 속성을 "true"로 설정하고 dragstart 이벤트 등록
  • 드롭 영역Dropzone으로 설정할 DOM Element에 dragover 이벤트와 drop 이벤트 등록

자바스크립트 드래그 앤 드롭 씨리-즈


이 글에서 말하고 싶은 거는...

  • 특정 위치에 드래그 앤 드롭으로 엘리먼트Element 붙여넣기(배치하기)
  • 드래그 앤 드롭 중 드롭 대상 컨테이너 내에서 상대적 위치 구하기
  • 드래그 앤 드롭으로 "스티커 붙이기" 구현하기
  • 드롭 위치에 그대로 엘리먼트 내려놓기

등등...인데 이 중에서 글 제목으로 적절한검색 엔진에 더 잘 걸릴 듯한 것이 무엇일지 고민하다가 결국 제일 무미건조한(?) 제목으로 선정했는데, 아무쪼록 제가 남기고자 하는 내용을 찾는 분들께서 답을 찾아가실 수 있길 바랍니다🙏

서론

현재 과정 막바지를 향해 달려가고 있는 SW마에스트로 본과정 중 개발 중인 팀 프로젝트에서 드래그 앤 드롭(Drag and Drop)을 써야하는 기능이 하나 있었습니다. 간단히 말하자면 "편지를 꾸미기 위한 데코레이션 요소"였는데요, 그 요소 중 하나인 "스티커"를 현실에서 편지를 쓸 때 스티커를 사다가 손으로 붙이는 것 같이 드래그 앤 드롭으로 그 경험을 살리려고 했습니다. 즉, 스티커 아이템을 드래그하여 편지 영역(컨테이너) 내 어디서든 자유롭게 드롭하고 드롭된 위치에 스티커 아이템이 생성되어야 합니다.

그런데... 전에 구현을 해 본 적이 있어야 말이죠. 다른 이벤트들은 다뤄본 적이 있더래도 드래그-드롭 쪽은 구현을 해 볼만한 프로젝트가 없었습니다. 그런 와중에  날먹 좀 쉽게 구현을 해보려고 라이브러리를 찾아본 결과 Shopify의 Draggable (소개 페이지가 매우 인상적이에요!), 웹 프레임워크로 Vue를 사용하고 있어 Vue.Draggable이라는 것까지 둘러봤는데, 둘 다 Element의 자유로운 Drag-and-Drop이 아닌 리스트 내 재배치쪽에 더 가까운 느낌이라 저의 목적에는 부합하지 못했습니다. 제가 덜 찾아본 걸 수도 있구요.

그래서 직접 구현하고 적용했던 경험을 공유하려고 합니다. 사실 그렇게 어렵진 않아요.

글로 쓰고 보는게 어려울 뿐이지

웹에서의 드래그 앤 드롭

사실 드래그 앤 드롭 상호작용에 관련된 이벤트는 현대적인 웹 브라우저에 이미 충분히 구현되어 있습니다. 보통 이러한 이벤트는 서론에서 말했듯이 리스트 컨테이너 내에서 사용자 상호작용으로 아이템을 재배치하는데 사용되기도 하고, 데스크톱의 파일을 클라우드 서비스에 "끌어다 놓기"로 업로드할 때에도 사용됩니다.

드래그 앤 드롭의 본질?이나 작동 방식은 제가 설명하는 것보단 다른 어른들이 말하는 것이 더 정확하고 풍부할 것 같네요🤣. 드래그 앤 드롭 관련 API/이벤트에 대해서는 우리 모질라 형님의 문서를 참고하셔도 좋습니다.

HTML 드래그 앤 드롭 API - Web API | MDN
HTML 드래그 앤 드롭 인터페이스는 파이어폭스와 다른 브라우저에서 어플리케이션이 드래그 앤 드롭 기능을 사용하게 해줍니다. 이 기능을 이용해 사용자는 draggable 요소를 마우스로 선택해 droppable 요소로 드래그하고, 마우스 버튼에서 손을 뗌으로써 요소를 드롭할 수 있습니다. 드래그하는 동안 draggable 요소는 반투명한 채로 마우스 포인터를 따라다닙니다.

우리가 모질라 형님의 문서에서 주의깊게 보면 되는 부분은, dragstart 이벤트, drop 이벤트, DragEvent 인터페이스, DataTransfer 인터페이스, draggable 속성 정도입니다. 그 외 이벤트나 인터페이스들은 모질라 문서 또는 타 블로그 글에서 잘 정리된 글을 찾아볼 수 있을거에요.

본격적으로 시작~

예시 화면으로 실제 SW마에스트로 팀 프로젝트로 개발 중인 서비스를 사용합니다.

드래그 대상인 스티커와 드롭 대상인 편지 컨테이너

왼쪽에 있는 아기자기한 그림들이 드래그 대상인 "스티커", 오른쪽에 있는 내용이 채워지길 바라고 있는 드롭 대상인 "편지 영역(컨테이너)"입니다.

최대한 순수 HTML/JavaScript로 예시 코드를 작성했지만, 편의상 예시 코드에도 Vue 전용 속성이 포함되어 있을 수 있습니다. 또한, 예시 코드만으로는 위 페이지를 그대로 만들 수 없습니다. 참고 바랍니다!

드래그 대상Element을 드래그 가능하게 만들기

먼저 해야 할 일은, 각 스티커 영역를 드래그할 수 있게 만드는 것이죠. HTML Element가 드래그 가능하다고 브라우저에 알려주기 위해서는 draggable 이라는 속성을 쥐어주면 됩니다! 그럼 해볼까요?

<!-- 개별 스티커 아이템 -->
<img src="**"
     alt="**"
     class="sticker"
     draggable="true" /> <!-- draggable="true" 추가 -->
참고로, draggable 속성은 Boolean 타입이 아니기 때문에 속성 값 설정("auto", "true" 또는  "false")이 필수입니다. MDN draggable 문서의 상단을 참고하세요.
Element가 드래그 가능해진 모습

draggable 속성이 아니더라도 기본적으로 드래그가 가능한 경우가 있습니다. 대표적으로 텍스트 선택 영역, 이미지(<img> 등), 링크(<a> 등)와 같은 경우입니다. 그러한 태그가 아닌데 드래그 가능하도록 하기 위해선 속성을 지정해주어야 합니다.

draggable 속성을 설정했음에도 드래그가 되지 않는다면, CSS -webkit-user-drag 스타일none으로 적용되어있는지 확인해보세요.

기본적으로 드래그가 가능하지 않은 DOM 요소는 draggable만 설정해서는 실제로 드래그가 작동하진 않아요. MDN 드래그 앤 드롭 API 문서의 #어떤 것이 draggable인지 확인하기에서,

하나의 요소를 draggable로 만들기 위해서는 draggableondragstart전역 이벤트 핸들러를 아래 예제 코드와 같이 추가해야합니다.

라고 언급된 바와 같이, dragstart 이벤트를 등록해주어야 합니다.

/* 모든 .sticker에 dragstart 이벤트 등록 */
document.querySelectorAll(".sticker").forEach((element) => {
    // dragstart 이벤트 핸들러의 내용은 중요하지 않고, 이벤트가 등록되어 있기만 해도 됩니다.
    element.addEventListener("dragstart", () => {});
});

코드를 적용하고 스티커를 클릭-드래그 해보면 위 스크린샷처럼 드래그가 가능할거에요.

이 상태에서 백날 편지 영역에 끌어놓는다고 해도 놓아지지 않습니다. 편지 영역은 아직 스티커를 받아줄 마음의 준비가 되어있지 않거든요!

드롭 컨테이너Dropzonedrop 이벤트 등록하기

편지 영역이 스티커를 받아주기 위해선, 마찬가지로 이벤트를 등록해주어야 합니다. MDN 드래그 앤 드롭 API 문서의 #드롭 지역 정의하기 문단에서,

특정 요소가 드롭 지역 혹은 droppable로 만들기 위해서는 해당 요소가 ondragoverondrop 이벤트 핸들러 속성을 가져야합니다.

라고 적혀져 있듯이, drop 이벤트와 dragover 이벤트가 같이 등록되어 있어야 정상적으로 드롭 가능한 지역(= Dropzone)으로 인식한다고 하네요. 드래그 대상Draggable을 만들 때와 비슷하죠. 이제 이벤트를 등록해 봅시다!

<!-- Dropzone으로 사용할 편지 영역(컨테이너) -->
<div id="letter-container">
    ...
</div>
/* 편지 영역에 dragover 이벤트 등록 */
document.querySelector("#letter-container").addEventListener("dragover", (event) => {
    event.preventDefault(); // 추가적인 이벤트 발생을 방지합니다.
    event.stopPropagation(); // 하위 element로 이벤트가 전달되는 것을 방지합니다.
    console.log("dragover", event); // 테스트를 위해 console에 이벤트 오브젝트를 출력해봅니다.
});

/* 편지 영역에 drop 이벤트 등록 */
document.querySelector("#letter-container").addEventListener("drop", (event) => {
    event.preventDefault();
    event.stopPropagation();
    console.log("drop", event);
});

저장하고 나서 페이지를 새로 고친 후, 편지 영역에 스티커를 드래그 앤 드롭하면서 개발자 도구 콘솔을 눈이 빠질 듯이 지켜봅시다!

dragover와 drop 이벤트 로그

위와 같은 로그를 확인할 수 있다면, 편지 영역을 성공적으로 드롭 영역으로 만들었다는 뜻입니다! 박수~~👏👏🏻👏🏼👏🏽👏🏾👏🏿

이벤트를 등록하고 드래그 가능한 요소Draggable와 드롭 영역Dropzone까지 잘 만들었으면 남은 건 이 이벤트들을 활용하는 로직만 만들어주면 됩니다!
다르게 말하자면, 개발 환경은 준비가 되었으니 본격적으로 개발에 들어가면 되는거죠! 신난다! 일이다!

이번 주제의 핵심인 "특정 위치에 드롭해서 배치하기(붙여넣기)"다음 글에서 다룹니다. 꼭 읽어주세요!

예시 코드 중에 오류가 있다면 댓글로 제보 부탁드립니다 :D