프로젝트를 하며, fileUpload 에 대하여 사용해야 하는 상황이 생겼다.
이미지 업로드랑 파일 업로드, 파일 다운로드, 파일 미리보기까지 구현해야하는 상황에서, 간단하게 HTML의 요소 중 input file을 사용해서 진행해보려고 하였다. (기본이 가장 중요하다.)
<label
htmlFor="file"
className=" bg-neutral-500 text-white p-2 text-center rounded-md text-sm
hover:bg-neutral-700 hover:text-white
active:bg-neutral-500
focus:bg-neutral-700
cursor-pointer"
>
<i className="xi-plus"> 문서</i>
</label>
<input
type="file"
name="file"
id="file"
multiple
onChange={handleFileUpload}
style={{ display: "none" }}
/>
일단 react 에서 이런식으로 input type을 file로 지정해주었다. 그리고 우리가 이렇게 input type을 file로 지정해주었을 때,
보기싫은 파일 찾기 이런걸 꼭 눌러야하고 아무튼 디자인적으로 매우 예쁘지 않은 파일첨부 input이 뜬다.
약간 이런 느낌으로 뜨기 때문에 매우 마음에 안든다. 그래서 label 을 설정하여 label for 를 이용해 input file의 id 값을 for에 작성하면
서로 연결되어서 label을 클릭하면 file을 클릭한 것 처럼 되게 만들어주었다.
그리고 저 보기 싫은 디자인은 display none 해주자. (다른 방법도 충분히 있을텐데, 이런것에 그렇게 시간을 쓰고싶진 않았음.)
그리고 multiple로 파일들을 다중선택 할 수 있게 만들어준다.
https://developer.mozilla.org/ko/docs/Web/HTML/Element/Input/file
<input type="file"> - HTML: Hypertext Markup Language | MDN
file 유형의 <input> 요소에는 저장 장치의 파일을 하나 혹은 여러 개 선택할 수 있습니다. 그 후, 양식을 제출해 서버로 전송하거나, File API를 사용한 JavaScript 코드로 조작할 수 있습니다.
developer.mozilla.org
사실 그냥 MDN 문서를 보면 잘 나와있다.
여기서 그냥 형식같은거 보면서 원하는거 추가하면 된다. 한국어로 되어있어서 편함
onChange (파일에 관해서 파일을 추가 / 삭제 하는 시점마다) 에 handleFileUpload 함수를 만들어서 파일을 저장해준다.
const [files, setfile] = useState([]);
const handleFileUpload = (e) => {
const docsFileArr = e.target.files;
let docsfiles = [];
let file;
let filesLength = docsFileArr.length > 10 ? 10 : docsFileArr.length;
for (let i = 0; i < filesLength; i++) {
file = docsFileArr[i];
docsfiles[i] = file;
files.length === 0
? setfile([...docsfiles])
: setfile(files.concat([...docsfiles]));
}
e.target.value = "";
};
일단 setstate 해준다. (저장을 해야하기 때문에~)
이렇게 e.target.files로 지정해준 file들을 Arr로 받은 다음에, 파일 제한을 위해 10개 이상은 10개로 limit 해준다
그런 다음에 지정해준 길이로 for문을 돌린다. 그리고, for문을 돌리는 file 각각 하나하나를 미리 만들어 준 변수인 file에 넣고,
만들어준 배열 안에도 차곡차곡 넣어준다.
그리고, files.length 즉 state 안에 아무것도 없다면, docsfiles을 넣어주고, 만약에 있다면, files.concat으로 그 files의 다음에 넣어준다.
그러고나서 e.target.value = "" 를 해주어서 비워주어야 동일한 파일을 올렸을 때도 file이 잘 들어가진다.
파일 업로드는 그래도 그나마 쉽게 구현을 하였다. 조금만 생각해보면 구현할 수 있었다.
이미지를 업로드 하는 부분에서는 꽤 많이 애를 먹었는데, 아무래도 프로젝트 구조 상 모바일에서도 작동해야하기 때문에, 모바일 갤러리에서 사진을 업로드 하면 회전이 되는 버그가 발생했다.
그래서 검색을 해본결과 loadImage라는 라이브러리를 사용해서 모바일 상에서 회전을 막아준다고 한다.
https://github.com/blueimp/JavaScript-Load-Image
GitHub - blueimp/JavaScript-Load-Image: Load images provided as File or Blob objects or via URL. Retrieve an optionally scaled,
Load images provided as File or Blob objects or via URL. Retrieve an optionally scaled, cropped or rotated HTML img or canvas element. Use methods to parse image metadata to extract IPTC and Exif t...
github.com
문서가 보기 좀 어렵지만, 그래도 이정도면 어떤 원리로 동작되는지도 잘 알 수 있다.
어쨌든 이미지 회전 문제로 인해 적용을 해 보았다.
function rotateImageFile(file) {
return new Promise((resolve, reject) => {
loadImage(file, { meta: true, canvas: true, orientation: true }).then(
(img) => {
img.image.toBlob((blob) => {
resolve(new File([blob], `${file.name}.jpg`));
}, "image/jpeg");
}
);
});
}
const handleImageUpload = (e) => {
e.preventDefault();
let input = document.createElement("input");
input.type = "file";
input.accept = "image/*";
input.multiple = "multiple";
input.capture = "camera";
document.body.appendChild(input);
input.click();
input.onchange = function (e) {
const imageFileArr = e.target.files;
const loadedImages = Array.from(imageFileArr).map((file) =>
rotateImageFile(file)
);
Promise.all(loadedImages).then((result) => {
Array.from(result).forEach((file) => {
setimages("images", file);
});
}
document.body.removeChild(input);
});
};
e.target.value = "";
};
이거는 카메라로 촬영을 할 수 있도록 input file에 다양한 옵션을 넣었고, js로 input element를 만들었다. 원래는 이렇게 안하고 label 사용해서 하고 싶었는데, 추후의 이미지 삭제라던가 다른 부분을 신경써서 만들어야 했으므로 어쩔 수 없이 이렇게 만들었었다.
input과 관련하여 옵션을 지정해주고, 따로 rotate 해주는 함수를 만들어서 loadImage 라이브러리를 이용하여 비동기화로 promise를 지정해서 만들어주었다. 그렇게 회전을 막아준 다음 Promise.all로 이미지를 다 받아오면, 그 때 이미지를 저장할 수 있도록 만들어 주었다.
input 역할이 끝나면 input도 지워주고, 아까 동일한 파일을 다시 한번 업로드 할 수 있도록 value 값도 지워주었다.
이렇게 이미지, 파일들이 저장이 되었으면, 브라우저 화면에도 이미지 미리보기나, 하다못해 파일 이름이라도 출력되어야 잘 저장이 되었구나를 확인할 수 있다.
{files.map((file, index) => (
<div className="flex" >
<li
key={file.name}
className="list-none pt-2 pl-2 mt-1 text-left"
>
{file.name}
</li>
</div>
))}
그건 간단하게, file 같은 경우에는 만들어 준 state에 담긴 files를 map (forEach를 사용하는게 더 명확할듯.) 으로 돌려서 file.name을 불러오면 간단하게 이름이 뜬다.
{imgs.map((image) => image.fileStorageId)
.map((image, index) => (
<div key={index} className="grid">
<DeleteButton
key={index}
value={image}
onClick={handleimageDelete}
/>
<img
key={image}
value={image}
src={image}
className=" p-1 object-cover h-[100%] w-[100%] "
></img>
</div>
))}
이미지 같은 경우도 그냥 src 자체에 image를 넣어주면 된다
'React' 카테고리의 다른 글
React - axios 활용하기 (0) | 2023.02.16 |
---|---|
React - CORS 오류에 대하여 (0) | 2023.02.16 |
React tailwind 설정 세팅 및 활용 방법 (0) | 2023.02.16 |
react youtube-clone fetch web APIs와 Axios 라이브러리 (0) | 2022.09.30 |
react youtube-clone 목록으로 돌아가기 (0) | 2022.09.29 |