일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- SCSS forward
- Vue
- Spacer
- KDT 프로그래머스
- 다른컴퓨터에서 git사용
- 프로그래머스 K_Digital Training
- SCSS extend
- 프로그래머스 프론트엔드 데브코스
- git 같은계정 다른 컴퓨터
- 쌓임맥락
- flex
- KDT 프로그래머스 데브코스 프론트엔드
- intersection opserver
- 고양이 사진 검색기
- postcss
- vuex map
- netlify redirect
- SCSS use
- 리액트
- 리스트 렌더링
- SCSS import
- vue 지역 컴포넌트
- react next
- 프로그래머스 데브코스 프론트엔드
- 폼 입력 바인딩
- vue mixin
- nextjs사용법
- 프로그래머스 데브코스
- 이벤트 수식어
- vue 이벤트 수신
- Today
- Total
혼자 적어보는 노트
[React] Drag and Drop 이벤트 적용해보기 본문
종종 사이트에서 드래그&드롭 이벤트가 보이길래
투두리스트를 작업하면서 나도 해당 기능을 적용해보기로 했다.
라이브러리를 사용해야 될 것만 같았는데
HTML 드래그앤 드롭 API가 존재 한다는 것을 알게되었다.
여러 블로그나 글을 읽고 해본 다음에 공식문서를 보는 것이 도움이 되었다.
+ 참고블로그
드래그 api를 사용할 때 필요한 것.
1. draggable 속성 추가.
드래그 api를 사용하려면 html 태그 안에 draggable이라는 속성을 넣어주어야 한다.
<li draggable></li>
2. 이벤트 지정.
사용할 이벤트를 지정해준다.
주요 이벤트 이름은 아래와 같다.
drag
드래그 할 때 발생
dragstart
드래그하기 시작했을 때 발생
dragend
드래그를 끝냈을 때 발생
dragenter
드래그한 요소가 드롭 대상 위에 올라갔을 때 발생.
dragleave
드래그하는 요소가 드롭대상에서 벗어났을 때 발생
dragover
드래그한 요소가 드롭대상 위로 지나갈 때 발생 (수백 밀리초 마다 발생)
drop
드래그한 요소가 드롭대상에 드롭했을 때 발생.
3. dataTransfer
모든 drag 이벤트에는 dataTransfer라는 드래그 데이터를 보유하고 있는데
무엇을 끌고 있는지 데이터의 유형을 설정 해 주어야 한다.
* 이것을 설정하면 드랍할 곳으로 데이터를 보내줄 수 있다.
event.dataTransfer.setData("text/html", event.target);
▼ dataTransfer 공식문서
https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types
예시)
$todoList.addEventListener("dragstart", (e) => {
e.dataTransfer.setData("id", e.target.id);
});
$todoList.addEventListener("drop", (e) => {
console.log(e.dataTransfer.getData("id")); // e.target.id의 값이 나타난다
});
4. dropEffect, effectAllowed
드래그 대상과 드롭되는 타깃이 어떤 동작을 수행할지 지정해야 한다.
dropEffect
드롭 이벤트에서 어떤 동작을 하는지 정할 수 있다.
* dragenter/dragover이벤트 핸들러에서 지정
none : 이곳에 드롭 불가
move : 드래그 대상을 드롭 타깃으로 옮김
copy : 드래그 대상을 드롭 타깃으로 복사
link : 드래그 대상이 URL이라면 드롭 타깃이 이동
effecAllowed
드래그 대상에 허용할 dropEffect를 정할 수 있다.
* dragstart이벤트 핸들러에서 지정.
uninitialized : 아무것도 지정안함
none : 아무것도 허용안함
copy : dropEffect copy만 허용
link : dropEffect link만 허용
move : dropEffect copy만 허용
copyLink : dropEffect copy, link 허용
copyMove : dropEffect copy, move만 허용
linkMove : dropEffect link, move만 허용
all : dropEffect값 모두 허용 / 기본값
5. 드롭 허용
드롭을 허용하려면 dragEnter, dragOver 이벤트를 취소하여 기본처리를 방지해야 한다.
const onDragOver = (event) => {
event.preventDefault();
};
위의 세가지를 설정하여 테스트로 작업해보았다.
const list = [
{ id: 1, name: "1. 할 일1" },
{ id: 2, name: "2. 할 일2" },
{ id: 3, name: "3. 할 일3" },
{ id: 4, name: "4. 할 일4" },
{ id: 5, name: "5. 할 일5" },
];
const DragAndDrop = (props) => {
const [lists, setLists] = useState(list);
const onDragOver = (event) => {
event.preventDefault();
console.log("DragOver");
};
const onDragStart = (event) => {
event.dataTransfer.setData("text/html", event.target); //데이터 유형 설정
console.log("DragStart");
};
const onDragEnd = (event) => {
console.log("DragEnd");
};
const onDrop = (event) => {
console.log("Drop");
};
const onDragLeave = (event) => {
console.log("DragLeave");
};
const onDragEnter = (event) => {
event.preventDefault();
console.log("DragEnter");
};
return (
<ul className="drag">
{lists.map((list, index) => (
<li
key={list.id}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
onDragEnter={onDragEnter}
onDragLeave={onDragLeave}
onDragOver={onDragOver}
onDrop={onDrop}
draggable
>
{list.name}
</li>
))}
</ul>
);
};
export default DragAndDrop;
dragStart - dragEnter - dragOver - dragLeave - drop - dragEnd
위 순서대로 콘솔에 나오는 것을 볼 수 있다.
const DragAndDrop = (props) => {
const [lists, setLists] = useState(list);
const [drag, setDrag] = useState(null); // 드래그를 하려는 요소를 담는 state
const onDragOver = (event) => {
event.preventDefault();
};
const onDragStart = (event) => {
setDrag(event.target); // 드래그 시작 시 데이터를 담는다
event.dataTransfer.setData("text/html", event.target); // 데이터 속성 설정
};
const onDragEnter = (event) => {
event.preventDefault();
let dragIndex = Number(drag.dataset.index); // 드래그하는 요소의 원래 인덱스
let targetIndex = Number(event.target.dataset.index); // 드래그후 지나가는 요소의 인덱스
let _lists = [...lists];
_lists[dragIndex] = _lists.splice(targetIndex, 1, _lists[dragIndex])[0];
// 지나가는 요소의 인덱스와 드래그한 요소의 인덱스를 이용하여 위치변경
setLists(_lists);
};
const onDragLeave = (event) => {
if (event.target === drag) {
event.target.style.visibility = "hidden"; // 머무를 때 요소를 안보이게 처리
}
};
const onDragEnd = (event) => {
setDrag(null);
event.target.style.visibility = "visible";
};
return (
<ul className="drag">
{lists.map((list, index) => (
<li
key={list.id}
data-index={index}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
onDragEnter={onDragEnter}
onDragLeave={onDragLeave}
onDragOver={onDragOver}
draggable
>
{list.name}
</li>
))}
</ul>
);
};
드래그 후 요소를 지나갈 때 마다 위치를 서로 바꾸어 주었는데
위와 같이 작업 시 각 li에서 순서대로 지나가면 문제가 없지만 드래그후 ul의 영역 바깥으로 이동했다가
드롭을 하면 원하는 결과가 나오지 않는다.
곰곰히 생각해보면 당연한 결과겠지만,
1번드래그 -> ul바깥쪽 이동 -> 다른요소들을 거치지않고 5번으로 바로 이동 시 1번과 5번이 뒤바뀜.
문제 해결
DragEnter코드 수정.
const onDragEnter = (event) => {
let dragIndex = Number(drag.dataset.index);
let targetIndex = Number(event.target.dataset.index);
let _lists = [...lists];
let _listItem = _lists[dragIndex];
if (dragIndex !== targetIndex) {
_lists.splice(dragIndex, 1);
_lists.splice(targetIndex, 0, _listItem);
setLists(_lists);
}
};
단순 위치만 바꾸어주는 코드가 아닌 드래그하는 요소와 드래그한 후에 닿게되는 요소의 index를 이용하여
드래그하고있는 index의 요소를 삭제해주고 드래그해서 닿는 위치에 추가를 해주는 식으로 변경하였다.
전체코드
const list = [
{ id: 1, name: "1. 할 일1" },
{ id: 2, name: "2. 할 일2" },
{ id: 3, name: "3. 할 일3" },
{ id: 4, name: "4. 할 일4" },
{ id: 5, name: "5. 할 일5" },
];
const DragAndDrop = (props) => {
const [lists, setLists] = useState(list);
const [drag, setDrag] = useState(null);
const onDragOver = (event) => {
event.preventDefault();
return false;
};
const onDragStart = (event) => {
setDrag(event.target);
event.dataTransfer.effectAllowed = "move";
event.dataTransfer.setData("text/html", event.target);
};
const onDragEnd = (event) => {
setDrag(null);
event.target.style.visibility = "visible";
};
const onDragLeave = (event) => {
if (event.target === drag) {
event.target.style.visibility = "hidden";
}
};
const onDragEnter = (event) => {
let dragIndex = Number(drag.dataset.index);
let targetIndex = Number(event.target.dataset.index);
let _lists = [...lists];
let _listItem = _lists[dragIndex];
if (dragIndex !== targetIndex) {
_lists.splice(dragIndex, 1);
_lists.splice(targetIndex, 0, _listItem);
setLists(_lists);
}
};
return (
<ul className="drag">
{lists.map((list, index) => (
<li
key={list.id}
data-index={index}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
onDragEnter={onDragEnter}
onDragLeave={onDragLeave}
onDragOver={onDragOver}
draggable
>
{list.name}
</li>
))}
</ul>
);
};
코드에 비해..
꽤 오랜시간 씨름했지만 그래도 나름 원하던 결과가 나왔다.
'React' 카테고리의 다른 글
[React] Drag and Drop 이벤트 응용해보기 / 문제해결 (0) | 2022.01.08 |
---|---|
[React] Redux 초기 세팅, 구조 with Ducks (0) | 2022.01.07 |
[React] firebase / 사용자 이메일 로그인 기능 구현해보기 (0) | 2022.01.04 |
[React] state데이터 object로 관리/수정하기 (0) | 2022.01.03 |
[React] 유튜브 API 사용법 / youtube API / axios (0) | 2021.12.28 |