더 알아보기/기능

[Next.js] Drag&Drop 기능 구현하기

은돌1113 2024. 1. 9. 09:44

이번에는 관리자 페이지에서 이미지를 업로드할 때 파일 선택 버튼 외에도 드래그 앤 드롭으로 이미지를 삽입할 수 있는 기능을 구현해보았다.

 

코드

아래 링크에 블로그와 기존에 구현했던 이미지 파일 변환 기능을 활용하여 구현하였다.

  • Test 페이지 (/pages/test.js)
import Head from "next/head"

import FileBox from "@/elements/library/fileBox/Test"
import { useEffect, useState } from "react"
import styled from "styled-components"

const Test = () => {
  const [imageFile, setImageFile] = useState(null)
  // AWS에 이미지를 업로드할 때 사용

  return (
    <TestCss>
      <Head>
        <title>Test</title>
        <meta keyword="test" />
        <meta content="test" />
      </Head>

      <FileBox setImageFile={setImageFile} />
      // 이미지 파일을 저장하기 위해 setState를 props로 전달
    </TestCss>
  )
}

const TestCss = styled.div`
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
`

export default Test
  • FileBox 컴포넌트 (/elements/library/fileBox/test.jsx)
    • onDragEnter : getInputProps()에 드래그된 경우 setHover를 true로 변환
    • onDragLeave : getInputProps()에 드래그가 없는 경우 setHover를 false로 변환
import { imageExtensions } from "@/common/imageExtensionFormat"
import Image from "next/image"
import { useEffect, useState } from "react"
import { useDropzone } from "react-dropzone"
import { IoMdClose } from "react-icons/io"
import styled from "styled-components"

const Test = (props) => {
  const { setImageFile, basicImage } = props
  // basicImage : 수정 시 기존에 등록한 이미지 src 전달 받아옴

  const [preview, setPreview] = useState(null)
  // 미리보기용
  const [hover, setHover] = useState(false)
  // 드래그 앤 드롭 시 hover CSS 변환용

  useEffect(() => {
    if (basicImage) {
      setPreview(basicImage)
    }
  }, [basicImage])

  const onDrop = (file) => {
    const reader = new FileReader()

    if (file) {
      reader.readAsDataURL(file[0])

      const extension = file[0].path.split(".")[1]

      if (imageExtensions.includes(extension)) {
      	// imageExtensions : 확장자 확인용
        const fileObject = URL.createObjectURL(file[0])
        const setUniqueKey = fileObject.split("/").pop() + "." + file[0].name.split(".").pop()

        setImageFile({
          key: setUniqueKey,
          fileObject: fileObject,
          file: file[0],
          fileName: file[0].name,
        })
      } else {
        alert("업로드할 수 없는 이미지입니다.")
        return
      }
    }

    reader.onload = () => {
      setPreview(reader.result)
    }
  }

  const { getRootProps, getInputProps } = useDropzone({
    onDrop, // drop된 경우 실행
    onDragEnter: () => setHover(true), // drag or hover된 경우 실행
    onDragLeave: () => setHover(false), // drag or hover 안된 경우 실행
  })

  const deleteFileHandler = () => {
    const result = confirm("이미지를 삭제하시겠습니까?")

    if (result) {
      setImageFile(null)
      setPreview(null)
      setHover(false)
    }
  }

  return (
    <FileBoxCss>
      <label htmlFor="imageFile" className="uploader">
        파일 선택
      </label>
      <div className="imageWrap">
        <div {...getRootProps()}>
          <input className="file" type="file" name="imageUrl" id="imageFile" accept="image/*" {...getInputProps()} />
          {preview ? (
            <div className="fileImage">
              <Image src={preview} width={250} height={250} alt="이미지" />
              <div className="shadow" />
            </div>
          ) : (
            <div
              className="noFileImage"
              onMouseOver={() => setHover(true)}
              onMouseLeave={() => setHover(false)}
              style={hover ? { border: "2px solid var(--work-sub-color)" } : { border: "none" }}
            >
              {hover ? (
                <Image src="/img/common/file.svg" width={24} height={24} alt="fileIcon" />
              ) : (
                <Image src="/img/common/plus.png" width={16} height={16} alt="plusIcon" />
              )}
            </div>
          )}
        </div>
        {preview && <IoMdClose className="deleteBtn" onClick={deleteFileHandler} />}
      </div>
    </FileBoxCss>
  )
}

const FileBoxCss = styled.div`
  .imageWrap {
    width: 250px;
    height: 250px;
    position: relative;
    margin-top: 12px;
  }

  .fileImage {
    position: relative;
    width: 250px;
    height: 250px;
    margin-bottom: 12px;
  }

  .fileImage img {
    border-radius: 8px;
  }

  .deleteBtn {
    color: white;
    font-size: 20px;
    position: absolute;
    top: 5px;
    right: 5px;
    z-index: 2;
  }

  .noFileImage {
    width: 250px;
    height: 250px;
    background: #f5f5f5;
    border: 1px solid #eee;
    display: flex;
    justify-content: center;
    align-items: center;
    margin-top: 12px;
    border-radius: 8px;
  }

  .file {
    display: none;
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
  }

  .uploader {
    width: 80px;
    height: 40px;
    background: blue;
    border-radius: 10px;
    font-size: 15px !important;
    font-weight: 600;
    color: white !important;
    display: flex;
    justify-content: center;
    align-items: center;
    padding-top: 0 !important;
  }
`

export default Test

 

참고

 

#12. React에서 Drag & Drop을 이용한 이미지 업로드

링크서비스 등록화면에서 Input Value을 가져오는 방법을 알았다. 그럼 이미지 등록을 위한 Drag&Drop 이미지 업로드를 제작해보자. #11. React화면에서 각 항목의 Input Value을 가져오기 #11. React화면에서

firstvalue.tistory.com

  • getRootPrpos, getInputProps, onDrop, onDropEnter, onDropLeave 이외에도 다양한 속성이 있어 참고하면 좋을 것 같다.
 

react-dropzone

 

react-dropzone.js.org