이번에는 관리자 페이지에서 이미지를 업로드할 때 파일 선택 버튼 외에도 드래그 앤 드롭으로 이미지를 삽입할 수 있는 기능을 구현해보았다.
코드
아래 링크에 블로그와 기존에 구현했던 이미지 파일 변환 기능을 활용하여 구현하였다.
- 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
참고
- getRootPrpos, getInputProps, onDrop, onDropEnter, onDropLeave 이외에도 다양한 속성이 있어 참고하면 좋을 것 같다.
'더 알아보기 > 기능' 카테고리의 다른 글
[StoryBook] Vue 3 + Typescript 프로젝트에 StoryBook 적용하기 (0) | 2024.09.27 |
---|---|
[Mockoon] Route에 paramer 설정하기 (0) | 2024.09.25 |
[Next.js] Grouping Pagination 만들기 (라이브러리 X) (0) | 2023.12.11 |
[Next.js] 드롭다운 영역 외 클릭 시 메뉴 닫기 (2) | 2023.11.27 |
[Next.js] 드래그 상태에서 css 조작하기 (2) | 2023.11.23 |
댓글