Never give up

React - Drag & Drop file 본문

WEB

React - Drag & Drop file

대기만성 개발자 2022. 8. 20. 01:25
반응형

이번에는 리액트로 파일 드래그 앤 드롭을 구현해봤습니다

 

계속 js만 써왔는데, 나중에 ts도 필요할 수도 있을거 같아서 일단 한번 사용해봤습니다

 

drag_drop.css

  /* 드래그 앤 드롭 */
  .div_images {
      padding: 10px;
      font-size: 0.8em;
      color: grey;
      margin: 10px 0 10px 30px;
      box-shadow: 0 0 5px #cccccc;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
  }

  .div_preview {
      display: flex;
      flex-direction: row;
      justify-content: center;
      padding: 10px;
  }

  /* 이미지 프리뷰 */
  .div_preview>img {
      height: 100px;
      width: 100px;
      cursor: pointer;
  }

  /* pdf 이미지 클릭 */
  .div_preview>div {
      cursor: pointer;
  }

  /* pdf 이미지 프리뷰 */
  .div_preview>div>embed {
      height: 100px;
      width: 100px;
      display: block;
      z-index: -1;
      position: relative;
  }

  /* 이미지 삭제 버튼 */
  .div_preview+button {
      border: none;
      color: white;
      background-color: rgb(223, 71, 71);
      cursor: pointer;
      margin-top: 10px;
  }

  .div_preview+button:hover {
      animation: outline 1s ease;
  }

  #input_file {
      display: none;
  }

  /* 이미지 업로드 버튼 */
  #input_file+label {
      border: 1px solid #cccccc;
      border-radius: 12px;
      padding: 0 10px 0 10px;
      cursor: pointer;
  }

  #input_file+label:hover {
      background-color: #cccccc;
      color: white;
  }

  @keyframes outline {
      from {
          outline: #e4e4e4 solid;
          outline-offset: 10px;
          transform: scale(1)
      }

      to {
          outline: transparent solid;
          outline-offset: 0px;
          transform: scale(1.05);
          box-shadow: 0 2px 10px #e4e4e4;
      }
  }

embed에 z-index -1로 설정한 부분은 embed onclick부분을 해결하기 위해 사용했고

 

animation은 심심해서 만든거 넣어봤습니다

 

drag_drop.tsx

import './drag_drop.css';
import React, { ChangeEvent, DragEvent, useState } from 'react';

export default () => {
    let [imageList, setImageList] = useState<Array<File>>([])

    // 이미지 파일 처리 input
    const onInputFile = (e: ChangeEvent<HTMLInputElement>) => {
        e.preventDefault()

        handleFiles(e.target.files);
    };

    // 이미지 파일 처리 ondrop
    const onDropFiles = (e: DragEvent<HTMLDivElement>) => {
        console.log({ e }, e.dataTransfer.files)
        e.preventDefault()

        handleFiles(e.dataTransfer.files);
    };

    const handleFiles = (files: FileList) => {
        let fileList: Array<File> = [];

        if (files.length > 2) {
            alert(`이미지 개수가 초과되었습니다.업로드 된 이미지 개수 : ${files.length}개 (최대 2개)`
            );
            return;
        }

        for (let i = 0; i < files.length; i++) {
            const file: File = files[i];

            const format: string = `${file.name.split(".").slice(-1)}`.toUpperCase();

            if (
                format === "JPG" ||
                format === "JPEG" ||
                format === "PNG" ||
                format === "PDF"
            ) {
                fileList = [...fileList, file];
            } else {
                alert(`이미지 포맷을 확인해주세요.업로드 된 파일 이름 ${file.name} / 포맷 ${format}`
                );
                return;
            }
        }

        if (fileList.length > 0) {
            setImageList(fileList)
        }
    };

    // 없으면 drop 작동안함...
    const dragOver = (e: DragEvent<HTMLDivElement>) => {
        e.preventDefault()
        // console.log({ e });
    };

    const getImageUrl = (e: File) => {
        return URL.createObjectURL(e);
    };

    // 이미지 클릭시 새로운 화면
    const openImgFile = (e: File) => {
        window.open(
            getImageUrl(e),
            `${e.name}`,
            "height= 600, width= 600, location=0, titlebar=0 , menubar=0, status=0, toolbar=0"
        );
    };

    return (
        <div className='div_images'
            onDrop={onDropFiles}
            onDragOver={dragOver}
        >
            {imageList.length === 0 ? <>
                <p>이미지 파일을 박스 안으로 드래그 혹은 버튼을 클릭 해주세요.</p>
                <p>(파일 최대 2개, 허용 포맷: jpg/png/pdf)</p>
                <input id="input_file" type="file" multiple onChange={onInputFile} />
                <label htmlFor="input_file">이미지 업로드</label>
            </> : <><div className="div_preview">
                {imageList.map((e, i) => {
                    return (
                        <>
                            {e.type.includes('pdf') ?
                                <div key={`img-${i}`} onClick={() => openImgFile(e)}>
                                    <embed
                                        src={getImageUrl(e)}
                                        type={e.type}
                                    />
                                </div> :
                                <img
                                    key={`img-${i}`}
                                    src={getImageUrl(e)}
                                    alt=""
                                    onClick={() => openImgFile(e)}
                                />}
                        </>
                    )
                })}
            </div>
                <button onClick={() => {
                    setImageList([])
                }}>이미지 삭제</button>
            </>}
        </div>
    )
}

먼저 이미지 업로드시 상태관리를 해주기 위해 imageList부분을 정의해주고

 

드래그 앤 드롭을 사용할 부분에 onDrop, onDragOver를 등록해줍니다

(여기서 dragOver은 딱히 기능을 하지는 않는데 없으면 작동이 안됩니다..)

 

그리고 내부에 이미지가 있을 때 없을때 구분동작으로 간단하게(?) 만들어봤습니다

 

추가로 드래그 앤 드롭이 작동 안하는 환경을 위해 간단하게 버튼도 만들어줬습니다

ex) 모바일, 우분투(간헐적으로 안됨..)

 

그리고 나서 handleFiles에서 처리를 하는데

 

개수지정 그리고 파일 타입 지정등을 원하는대로 해주고

 

문제 없으면 setImageList로 이미지를 넣어주고

 

해당 파일들로 미리보기를 만들어주는데

 

image파일들은 url로 변경 후 img태그 src에 넣어주면 간단하게 작동되지만

 

pdf, docx 기타 등등 파일은 iframe, embed, object를 사용해야됩니다

(필자는 embed 사용)

 

그리고 onClick부분이 작동이 안되서 따로 처리를 해줘야됩니다

 

해당 예제에서는 z-index를 이용했는데

 

pointer-events: none;으로 처리하는 방법도 있습니다

 

(전체 소스 코드 : https://github.com/devmemory/drag_drop)

 

작동하는 부분을 확인해보면 다음과 같습니다

< 드래그 앤 드롭 동작부분 >
< 이미지 클릭시 미리보기 >

 

포스팅 할게 참 많은데 시간과 체력이 부족한거 같습니다..

< Not enough energy >

추가로 ts쓰면서 느꼈는데, 정말 타입처리가 중요한 부분만 지정하고

 

event처럼 타입도 길고 사용하는 부분이 적은곳에서는 any를 사용하는게

 

생산성에 조금 더 도움이 되지 않을까 합니다

반응형
Comments