React 이미지 끌어서 놓기, 드래그 앤 드랍, 틀 만들기2(이미지 여러개 끌어서 놓기)

김범기·2024년 7월 15일
0
post-thumbnail

개요

저번에 이미지 끌어서 놓기를 시도했다.
음. 그런데 하나만 놓기는 아쉬우니 여러개를 끌어서 놓을 수 있게 하고, 또 그에 대한 관리를 해주자.
간단하게 용량과 파일 타입, 갯수를 제한 시키자.

React 이미지 끌어서 놓기, 드래그 앤 드랍, 틀 만들기1(이미지 1개 끌어서 놓기)

실행

저번에 한 개를 놓는 것을 성공했고, 이번에는 여러개를 가능하도록 하자.

변수

변수부터가 달라져야한다.

우선 난 파일 하나당 용량, 총 용량, 총 갯수 를 관리하고 싶었기에 변수를 다음 처럼 사용했다.

const [isDragOver, setIsDragOver] = useState(false)
  const [files, setFiles] = useState([])
  const [maxSize, setMaxSize] = useState(10) //파일 업로드 가능 용량 mb기준
  const [maxTotalSize, setMaxTotalSize] = useState(10) //총 업로드 가능 용량 mb 기준
  const [maxFileNum, setMaxFileNum] = useState(10)  // 최대 업로드 가능 파일 갯수
  const [currentTotalSize, setCurrentTotalSize] = useState({"byteSize":0, "size": 0, "unit" : 'B'}) // 현재 업로드한 파일의 총 용량
  
  const allowFileTypes = [
    'image/png',
    'image/jpg',
    'image/jpeg',
    'image/gif',
  ]

여기서 currentTotalSize의 byteSize와 size로 구분한 이유는 byteSize를 이용해서 데이터가 추가 삭제 될 때마다 총 용량을 확인하고 변경하기 때문이다.

drag 사용

style은 틀을 잡기 위한 것이라 inline 방식으로 사용했다.

html

우선 html코드 먼저 보자.

<div>
      <div
        onDragOver={(e) => {handleDragOver(e)}}
        onDragLeave={() => {handleDragLeave()}}
        onDrop={(e) => {handleDrop(e)}}
        style={{
          border: isDragOver ? '2px dashed blue' : '2px dashed gray',
        }}
      >
        {files.length ? (
          <div className='' style={{width: "600px", height: '600px', display:'flex', flexDirection:'column', justifyContent:'center', alignItems:'center'}}>
            {files.map((theFile, i1) => (
              <div key={i1}>
                <span>{theFile[0].name}</span> <span>{theFile[1]} {theFile[2]}</span> <button onClick={() => {cancelFile(i1)}}>X</button>
              </div>
            ))}
          </div>
        ) : (
          <div className='' style={{width: "600px", height: '600px', display:'flex', flexDirection:'column', justifyContent:'center', alignItems:'center'}}>
            <p>이미지를 끌어서 올려주세요.</p>
          </div>
        )}
      </div>
      {files.length
        ? 
          <div>
            <p>{currentTotalSize.size} {currentTotalSize.unit}</p>
          </div>
        : <></>
      }
      <button onClick={() => {handleUpload()}}>Upload</button>
    </div>

files 변수를 [] 로 두었기 때문에 files의 유무가 아닌 files.length를 통해 파일 유무를 확인하도록 했다.

또한 files.map()을 이용해서 어떤 파일을 추가했는지, 용량은 얼마인지를 사용자가 확인할 수 있게 했다. 그리고 인덱스를 이용해서 cancel()할 수 있게 설정을 했다.

또한 총 용량을 파악할 수 있도록

<p>{currentTotalSize.size} {currentTotalSize.unit}</p>

를 이용해주기로 했다.

js

이제 함수 부분을 보자.


  const handleDragOver = (event) => {
    event.preventDefault()
    setIsDragOver(true)
  }

  const handleDragLeave = () => {
    setIsDragOver(false)
  }

  const handleDrop = (event) => {
    event.preventDefault()
    setIsDragOver(false)
    // for문 동안 작업해줄 변수
    let tmpFiles = [...files]
    // 최대 사이즈 확인
    let tmpTotalSize = currentTotalSize.byteSize

    // 1KB 2**10 1MB는 2**20 바이트 1GB는 2**30
    for(let i = 0; i < event.dataTransfer.files.length; i++){
      const thisFile = event.dataTransfer.files[i]
      // 파일 사이즈, 파일 총 용량, 파일 갯수를 확인
      if(thisFile.size <= maxSize * 2**20 && (tmpTotalSize + thisFile.size <= maxTotalSize * 2**20) && tmpFiles.length < maxFileNum){
        if(allowFileTypes.includes(thisFile.type)){
          // 사이즈와 단위
          const sizeUNit = handleFileSize(thisFile.size)
          const inputV = [thisFile, ...sizeUNit]
          tmpFiles = [...tmpFiles, inputV]
          tmpTotalSize += thisFile.size
        }else{
          alert('이미지 파일만 업로드 가능합니다.')
          break
        }
      }else if(tmpFiles.length >= maxFileNum){
        alert(`최대 ${maxFileNum}개의 파일을 등록할 수 있습니다.`)
        break
      }else{
        alert(`각 파일 및 전체 파일의 용량은 10MB를 초과해 등록할 수 없습니다.`)
        break
      }
    }
    // for문 이후 files 정리
    setFiles([...tmpFiles])
    const value = handleFileSize(tmpTotalSize)
    setCurrentTotalSize({
      "byteSize" : tmpTotalSize,
      "size" : value[0],
      "unit" : value[1]
    })
  }

  const handleUpload = () => {
    // 파일 업로드 로직 구현
    if(files.length){
      console.log('업로드한 파일들', files)
    }else{
      alert('파일을 넣은 후 진행하세요.')
    }
  }

  const handleFileSize = (theFileSize) => {
    const UNITS = ['B', 'KB', 'MB', 'GB', 'TB']
    let i = 0
    // let theFileSize = theFile.size
    while(theFileSize >= 1024 && i < UNITS.length - 1){
      theFileSize /= 1024
      i ++
    }
    if(i <= 1){
      return [Math.ceil((theFileSize)), UNITS[i]]
    }else{
      return [Math.ceil((theFileSize) * 100) / 100 , UNITS[i]]
    }
  }
  
  const cancelFile = (index) => {
    if(files.length){
      let tmpFiles = [...files]
      let tmpSize = tmpFiles[index][0].size
      let tmpTotalSize = currentTotalSize.byteSize - tmpSize
      const value = handleFileSize(tmpTotalSize)
      setCurrentTotalSize({
        "byteSize" : tmpTotalSize,
        "size" : value[0],
        "unit" : value[1]
      })

      tmpFiles.splice(index, 1)
      setFiles([...tmpFiles])
    }
  }

  useEffect(() => {
    if(files.length){
      console.log(currentTotalSize)
    }
  }, [files])
  

handleDragOver()와 handleDragLeave() 는 이전과 동일하다.

handleDrop()을 보게 되면, 우선 tmpFiles라는 변수를 사용했다. for문에서 바로

setFiles(...files, 어쩌고)

로 해도된다고 생각했지만, 동기적으로 작동하는 문제때문인지, 제대로 작동하지 않았다. 그래서 아예 변수를 하나 생성해주고 이를 이용해서 완성 시킨 tmpFiles를

setFiles(...tmpFiles)

의 방식을 이용했다.

또한 for문에서 &&를 이용해서 하나의 파일용량, 전체 파일용량, 전체 파일 갯수에 대해서 확인하고 3개가 모두 통과시에 파일을 추가했으면, 그렇지 못할 경우 break문으로 작동을 멈추게 했다.

파일의 사이즈는 이전과 동일하게 handleFileSize()를 사용했으며, cancel()시에는 총 용량에서 제거할 파일의 용량을 제한 것을 계산 한 후에, 해당 파일을

splice(index, 1)
splice(삭제할인덱스, 연속해서 몇개 지울래?)

을 이용해서 삭제했다.
useEffect()에서는 제대로 작동하는지를 확인하기위해 전체 파일 용량 크기를 출력하도록 했다.

잘 작동한다.

작동?!

잘 작동하는 모습을 볼 수 있다.

다음에 필요하면 가져다가 사용해야겠다ㅎㅎ

profile
반드시 결승점을 통과하는 개발자

0개의 댓글