이미지 파일을 업로드하고, 선택한 이미지 파일을 미리볼 수 있는 기능과 원하는 이미지 파일을 삭제하는 기능을 구현하면서 정리하고 싶어진 것이 많아 정리한다! (그나저나 반디캠 로고 시강이네;;)
이미지 미리보기 시 이미지가 세로로 길든, 가로로 길든, 정사각형 모양으로 크롭이 돼서 프리뷰가 제공되어야 한다. 이는 CSS로 간단하게 해결 가능하다.
background: `no-repeat center/100% url("${props.imgUrl}")`;
background-size: cover;
background-size: cover;
가 없을 시 세로가 긴 이미지는 정사각형으로 잘리지만, 가로가 긴 이미지는 정사각형으로 잘리지 않는 것을 확인할 수 있다. 이는, background-size의 default 값이 auto이기 때문이다.
이미지 파일들은 <input type="file">
을 사용해서 업로드한다. 이 부분은 쉬우니까 따로 언급하지 않고 지나가겠음!
Base 64란 8비트 이진 데이터(예를 들어 실행 파일이나, ZIP 파일 등)를 문자 코드에 영향을 받지 않는 공통 ASCII 문자열로 바꾸는 인코딩 방식을 가리키는 개념이다.
별도 이미지 파일 없이 위의 문자열 자체가 이미지이기 때문에 별도 이미지 파일이 없어도 브라우저에서 이미지를 렌더링할 수 있다는 장점이 있지만, 용량이 커진다는 단점이 있다.
const encodeFileToBase64 = (image: File) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(image);
reader.onload = (event: any) => resolve(event.target.result);
reader.onerror = (error) => reject(error);
});
};
File을 파라미터로 받아 인코딩하는 코드다. Promise 객체가 반환된다는 것을 유의하면 활용하기 매우 좋다.
const [files, setFiles] = useState<FileList>();
const [Base64s, setBase64s] = useState<{ image: File; url: any }[]>([]);
useEffect(() => {
if (files) {
setBase64s([]);
Array.from(files).forEach((image) => {
encodeFileToBase64(image).then((data) => setBase64s((prev) => [...prev, { image: image, url: data }]));
});
}
}, [files]);
그리고 FileList의 타입인 files의 state가 바뀔 때마다 encodeFileToBase64로 files를 인코딩한 값을 Base64에 넣어준다.
앞서서 선택한 파일들이 저장되는 files의 타입은 FileList
였다. 그런데 FileList는 read only로 지정되어 있기 때문에 update를 직접 할 수 없다.
DataTransfer을 사용하면 FileList를 update 할 수 있다. MDN DataTransfer 문서를 보면 Drag and drop API 라고 소개한다. MDN FileList를 보면 Drag and drop할 때 FileList를 사용한다고 되어 있다.
const deleteImage = (clickedImage: File) => {
const dataTranster = new DataTransfer();
Array.from(files)
.filter((file) => file !== clickedImage)
.forEach((file) => {
dataTranster.items.add(file);
});
setFiles(dataTranster.files);
};