일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- flex
- KDT 프로그래머스
- 고양이 사진 검색기
- KDT 프로그래머스 데브코스 프론트엔드
- 리스트 렌더링
- 프로그래머스 프론트엔드 데브코스
- 프로그래머스 데브코스 프론트엔드
- vue mixin
- Spacer
- SCSS extend
- Vue
- nextjs사용법
- SCSS import
- vuex map
- vue 지역 컴포넌트
- 폼 입력 바인딩
- 프로그래머스 데브코스
- 리액트
- netlify redirect
- 다른컴퓨터에서 git사용
- vue 이벤트 수신
- SCSS forward
- git 같은계정 다른 컴퓨터
- SCSS use
- 프로그래머스 K_Digital Training
- react next
- postcss
- 쌓임맥락
- intersection opserver
- 이벤트 수식어
- Today
- Total
혼자 적어보는 노트
[React] Drag and Drop 이벤트 응용해보기 / 문제해결 본문
https://jinist.tistory.com/92?category=930695
이전에 시도했던 Drag and Drop이벤트.
맛보기로 감을 잡았으니 내 프로젝트에 적용할 응용버전을 만들어 보기로 했다.
하고싶은 기능의 구현사항
1. 리스트 드래그 시 클릭한 리스트 불투명처리
2. 리스트 드래그 시 들어갈 위치에 border 표시
3. 드롭 시 해당 위치에 해당 리스트 삽입
import React from "react";
import { useState } from "react/cjs/react.development";
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 [clickTodo, setClickTodo] = useState(null);
const onDragOver = (event) => {
event.preventDefault();
return false;
};
const onDragStart = (event) => {
setDrag(event.target);
event.target.style.opacity = "0.3";
event.dataTransfer.effectAllowed = "move";
event.dataTransfer.setData("text/html", event.target);
};
const onDragEnd = (event) => {
setDrag(null);
event.target.style.opacity = "1";
};
const onDragLeave = (event) => {
event.target.classList.remove("bottom");
};
const onDragEnter = (event) => {
console.log(event.target);
event.target.classList.add("bottom");
};
const onDrop = (event) => {
let dragIndex = Number(drag.dataset.index);
let targetIndex = Number(event.target.dataset.index);
console.log(dragIndex, targetIndex);
let _lists = [...lists];
let _listItem = _lists[dragIndex];
if (dragIndex !== targetIndex) {
_lists.splice(dragIndex, 1);
_lists.splice(targetIndex, 0, _listItem);
setLists(_lists);
}
event.target.classList.remove("bottom");
};
const onClick = (event) => {
if (clickTodo) {
clickTodo.classList.remove("selected");
event.target.classList.add("selected");
} else {
event.target.classList.add("selected");
}
setClickTodo(event.target);
};
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}
onDrop={onDrop}
onMouseDown={onClick}
draggable
>
{list.name}
</li>
))}
</ul>
);
};
export default DragAndDrop;
처음엔 위와 같이 코드를 짰는데 border을 bottom에만 주다 보니
첫번 째 li를 밑으로 내리는 것에 대해서는 문제가 없었으나
중간위치나 맨 밑의 위치에서 리스트를 위로 올리는 부분에서 이상함이 느껴졌다.
const onDragEnter = (event) => {
let dragIndex = Number(drag.dataset.index);
let targetIndex = Number(event.target.dataset.index);
if (dragIndex < targetIndex) {
event.target.classList.add("bottom");
} else {
event.target.classList.add("top");
}
};
DragEnter에서 dragIndex와 targetIndex를 비교해서 border의 위치를 변경해주는 것으로 해결하였다.
처음에 생각한 대로 동작되었다.
간단하게 만들어 보고 이제 미니 프로젝트에 적용해보려고 하는데
여러가지의 문제가 발생했다.
1.
위의 테스트 작업에서는 li 안에 다른 HTML태그가 없었다.
하지만 적용하려는 프로젝트에서는 li안에 아이콘이나, 체크박스 등 다른 html태그를 넣어 놓았고
li를 드래그하면 dropEnter에서 event.target이 해당 하위요소들을 가르킨다는 것이다..
event.target을 우회하는 방법이나 이벤트를 막는 방법들을 찾아보았는데
pointer-events: none;을 하기에는 li안에 체크박스이벤트와
삭제버튼 클릭 이벤트가 있어서 막을 수 없었다.
그렇다고 dragStart 시에만 해당 이벤트에 pointer-events 속성을 넣는 것은 효율적이지 못할 것 같았다.
=> 문제해결: event.currentTarget속성을 발견했다.
이 속성을 이용하면 자식요소가 아닌 이벤트를 발생시킨 target을 받을 수 있다.
2.
위의 테스트 작업에서는 한 컴포넌트안에서 코드를 모두 구현했었지만,
이번에는 ul 이 담긴 컴포넌트<Todo />와 map을 돌려서 li를 출력하는 컴포넌트<TodoItem />로 나누어서 작업했다.
drag정보를 담는 drag state를 <TodoItem />에서 만들었는데
드래그를하고 다음 li로 마우스를 옮기면 dragEnter에서 drag 정보를 얻을 수 없는 것이다.
=> 문제해결: <TodoItem />를 감싸는 <Todo />컴포넌트에서 state를 생성하여 전달.
map을 돌린 컴포넌트 안에서 state를 생성하면 갯수만큼 state가 돌아간다. 당연한 원리였다.
3.
currentTartget으로 dragEnter, drop에서 li의 자식 요소가 선택되는것은 막았지만, (1번)
dragLeave를 마주칠 시 border효과를 해제하는 코드를 담았는데,
li를 드래그 한 상태에서 자식 요소에 커서가 닿을 경우
currentTarget은 li일 뿐이고, 해당 li를 벗어난다고 판단해서 그런지
dragLeave가 발생되어서 border가 지워지는 현상 발생.
=> 문제해결: dragEnter에서 current에 데이터를 담아준 후 데이터가 같지 않을 경우에만 실행.
const onDragLeave = (event) => {
if (event.currentTarget !== current) {
event.currentTarget.classList.remove("bottom");
event.currentTarget.classList.remove("top");
}
};
4.
문제들을 전부 해결하고 뒤늦게 발견한 부분인데
dragEnter시 drag하는 요소 기준으로 currentTarget의 인덱스가 높으면 (1 -> 5의 위치로이동)
border bottom을, 인덱스가 낮으면 (5 -> 1의 위치로 이동) border top을 주었는데
생각해보니 이건 인덱스기준이 아니라 내 드래그 커서 위치 기준으로 옮겨지는게 맞는 것 같다.
==> 문제해결: 드래그 중인 커서의 위치를 offsetHeight와 offsetY를 이용하여 계산한 후
drop시에 드래그중인 요소의 위치를 변경할 기준선(border)를 표시하고 drop시 그 위치에 맞게 바뀌도록 한다.
let current = null;
let height = 0;
let moveY = 0;
let movePosition = height / 2;
const onDragOver = (event) => {
event.preventDefault();
height = event.target.offsetHeight; // li의 height
moveY = event.nativeEvent.offsetY; // li기준으로 드래그 커서 위치
movePosition = height / 2; // 위,아래의 기준이 될 절반의 값
if (movePosition > moveY) {
event.currentTarget.classList.add("top");
event.currentTarget.classList.remove("bottom");
} else {
event.currentTarget.classList.add("bottom");
event.currentTarget.classList.remove("top");
}
return false;
};
const onDrop = (event) => {
let dragIndex = Number(drag.dataset.index);
let targetIndex = Number(event.currentTarget.dataset.index);
let _lists = [...todos];
let _listItem = _lists[dragIndex];
event.currentTarget.classList.remove("bottom");
event.currentTarget.classList.remove("top");
if (dragIndex == targetIndex) {
return;
}
if (dragIndex < targetIndex) {
if (movePosition > moveY) {
_lists.splice(dragIndex, 1);
_lists.splice(targetIndex - 1, 0, _listItem);
} else {
_lists.splice(dragIndex, 1);
_lists.splice(targetIndex, 0, _listItem);
}
}
if (dragIndex > targetIndex) {
if (movePosition > moveY) {
_lists.splice(dragIndex, 1);
_lists.splice(targetIndex, 0, _listItem);
} else {
_lists.splice(dragIndex, 1);
_lists.splice(targetIndex + 1, 0, _listItem);
}
}
dispatch(updateTodo(_lists));
};
중간중간의 문제들이 있어서 꽤 오랜시간 고생했지만 포기하지 않기를 잘한 것 같다
작동이 잘 안되서 여러가지를 사용해보고 실패하고 왜 안될까 생각을 많이 해본 것 같다.
결국은 딱 원하는대로 전부 구현되서 마음에 들었다. 코드는 좀 더 정리해 볼 예정이다.
다음 기능을 구현해야하기 때문에 바로 다른 드래그 예제를 해보진 못하겠지만
다음번에는 애니메이션이 함께 들어간 것도 만들어보고싶다.
drag api가 아닌 mouse이벤트로!
'React' 카테고리의 다른 글
[React] Todo List 기한 설정 기능 추가 해보기 (0) | 2022.01.11 |
---|---|
[React] 라이브러리 없이 캘린더 만들어 보기 (0) | 2022.01.10 |
[React] Redux 초기 세팅, 구조 with Ducks (0) | 2022.01.07 |
[React] Drag and Drop 이벤트 적용해보기 (0) | 2022.01.06 |
[React] firebase / 사용자 이메일 로그인 기능 구현해보기 (0) | 2022.01.04 |