모든 HTMLElement 상속 요소는 draggable이라는 boolean 타입 속성을 제공한다.
만약 draggable속성을 적용하지 않고 드래그했을 때는 일반적으로 다음과 같이 된다.
이제 draggable 요소를 다음과 같이 적용해보자. 엘리먼트 속성의 draggable을 적용한다.
<div draggable>Information</div>
이 요소를 클릭한 체 드래그했을 때, 다음과 같이 된다. (드래그 중 캡쳐)
이로 인해 흔히 사용하는 파일을 드래그 앤 드랍하는 기능을 구현할 수 있다.
이러한 드래그 앤 드롭에 관련된 주요 이벤트는 아래와 같다.
dragstart
드래그 작업이 시작될 때 발생하는 이벤트이다.
drag
드래그 중에 요소가 움직일 때 발생하는 이벤트이다. 드래그 도중 계속해서 발생하며, 드래그되는 요소의 위치 변경 등을 처리할 수 있다.
dragenter
드래그되는 요소가 드롭 대상 요소에 진입할 때 발생하는 이벤트이다. 드롭 대상 요소에 마우스가 올라갔을 때 발생한다.
dragover
드롭 대상 요소 위에서 드래그 도중에 발생하는 이벤트이다. 드롭 대상 위로 마우스가 움직일 때 발생하며, 이 이벤트를 처리하여 드롭이 허용되는지 여부를 결정할 수 있다.
drop
드래그된 요소가 드롭될 때 발생하는 이벤트이다. 드랍 이후 처리해야할 로직을 이곳에 구현하면 된다.
dragend
드래그 앤 드롭 작업이 끝났을 때 발생하는 이벤트이다. 드래그가 성공적으로 완료되었든 실패했든 간에 발생한다.
react는 드래그 앤 드롭 효과와 관련하여 React.DragEvent
인터페이스(타입)을 제공한다. 이 타입에서 중요한 속성은 dataTransfer
이다. 이를 통해 드래그 앤 드롭 동작 중에 데이터를 전달하거나 조작할 수 있다. 만약 파일을 드롭했을 때는 event.dataTransfer.files
속성을 통해 받아온 FileList
를 접근할 수 있다.
그리고 이러한 react 이벤트는 기본적으로 웹 브라우저 기본 구현 내용을 실행하게된다. 이 실행은 구현하려는 로직에 방해가 될 수 있기에 드래그앤 드롭 이벤트를 처리하려면 EventTarget
타입이 제공하는 preventDefault()
를 실행해야한다.
const onDrag = (e: DragEvent) => {
e.preventDefault();
// 로직 작성..
}
이러한 drag 이벤트를 바탕으로 유저의 이미지를 드롭시켜 UI에 그려주는 FileDrop
컴포넌트를 작성해 보자. 이미지는 2개 이상도 드롭이 가능하다.
// 이미지를 불러오는 유틸 함수
const imageFileReaderP = (file: Blob) => {
const promise = new Promise<string>((res, rej) => {
const fileReader = new FileReader();
fileReader.onload = (e: ProgressEvent<FileReader>) => {
const result = e.target?.result;
if (result && typeof result === 'string') {
res(result);
} else {
rej(new Error(`imageFilereaderP: can't read image File`));
}
}
fileReader.readAsDataURL(file);
});
return promise;
}
// 컴포넌트
const ImageDrop: FC = () => {
// 이미지 url들을 담는 state
const [imgUrls, setImgUrls] = useState<string[]>([]);
// drogOver 이벤트를 방지해야 drop 이벤트를 발생시킬 수 있다.
const handleDragOver = (e: DragEvent) => {
e.preventDefault();
}
// 이미지를 불러오는 함수를 통해 받아온 Promise를 기존 state에 더해준다.
const makeImageUrls = (files: File[]) => {
const promise = Array.from(files).map(imageFileReaderP);
Promise.all(promise)
.then(urls => setImgUrls(prev => [...prev, ...urls]))
}
// 이미지를 드롭했을 때 발생하는 이벤트이다.
const handleDrop = (e: DragEvent) => {
e.preventDefault();
const files = Array.from(e.dataTransfer.files);
files && makeImageUrls(files);
}
return (
<div>
<h1 onDragOver={handleDragOver} onDrop={handleDrop}>Drop Me!!</h1>
<div>
{
imgUrls?.map((url) => (
<>
<img
style={{
maxWidth: 300,
maxHeight: 200,
objectFit: 'contain'
}}
src={url} // url로 img의 src를 지정해준다
alt='none'
/>
<h4>{url}</h4> // url의 이름
</>
))
}
</div>
</div>
);
};
export default ImageDrop;