가. 이미지 파일을 올려서 프로필 사진을 변경할 것이다.
나. 이미지 용량이 매우 크기 때문에 압축을 해야 할 것이다.
다. 이미지가 업로드 되면 미리보기가 나와야 한다.
이미지 용량을 줄이기 위해서 라이브러리를 설치해야 한다.
browser-image-compression
설치 링크
라이브러리를 설치할 것이다.
터미널을 열고
npm i browser-image-compression
를 이용해서 설치해준다.
공통으로 사용할 기능이기 때문에 컴포넌트를 생성해주고 기본적인 CSS를 만들어 준다.
styled-components를 이용해서 만들어줄 것이다.
// imageUpload.tsx
import styled from "styled-components";
const ImageBox = styled.div`
width: 12rem;
height: 12rem;
position: relative;
border-radius: 50%;
margin-bottom: 4rem;
`;
const DefaultImage = styled(Image)`
cursor: pointer;
border-radius: 50%;
`;
const ImageUpload = () => {
const filePickerRef = useRef()
return (
<div>
{previewFile ? (
<ImageBox>
<DefaultImage
src={previewFile}
alt="프로필"
onClick={pickImageHandler}
fill={true}
/>
</ImageBox>
) : (
<ImageBox>
<DefaultImage
src={DefaultProfile}
alt="기본"
onClick={pickImageHandler}
fill={true}
/>
</ImageBox>
)}
<input
ref={filePickerRef}
type="file"
accept=".jpg,.png,.jpeg"
onChange={pickedHandler}
style={{ display: "none" }}
/>
</div>
)
}
return 부분에 previewFile은 이후 업로드 된 이미지를 미리보기를 만들고 미리보기가 있으면 미리보기를 띄우고 없으면 기본 이미지를 띄우기 위해 만들어 뒀다.
input을 이용해서 file을 업로드 하지만 매우 이쁘지 않기 때문에 ref를 이용해서 input을 클릭하지 않아도 업로드 기능을 할 수 있게 구현한다.
이제 이미지 업로드를 위한 사전준비가 끝났으니 기능을 구현해 본다.
우선 api로 보낼 데이터와 미리보기 위한 데이터 state를 만들어 준다.
// api로 보낼 데이터
const [file, setFile] = useState()
// 미리보기 데이터
const [previewFile, setPreviewFile] = useState()
이미지 압축을 위한 함수를 구현해준다.
이때 처음에 설치한 라이브러리를 사용해 준다.
import imageCompression from "browser-image-compression";
const pickedHandler = async (event) => {
// 타입스크립트를 위한 조건식
if(!event.currentTarget.files){
return
}
// 업로드된 파일 선택
const imageFile = event.currentTarget.files[0];
// file 형식의 데이터
console.log(imageFile);
// 이미지 압축 옵션들
const options = {
maxSizeMB: 0.5, // 용량 제한
maxWidthOrHeight: 120, // 가로세로 크기 제한
useWebWorker: true, // 멀티쓰레드 웹 워커 사용하고 메인 사용(뭔지 모름)
}
try{
// blob 데이터로 변환 및 압축
const compressedFile = await imageCompression(imageFile, options);
// blob에서 file로 변환
const convert = new File([compressedFile], imageFile.name, {
type: `${imageFile.type}`,
});
// api로 요청할 데이터 file 형식
console.log(convert, "convert");
// api로 보낼 데이터
setFile(convert);
// preview 만들어줄 데이터, web api new FileReader 이용
const reader = new FileReader();
// file 데이터를 url로 변환
reader.readAsDataURL(convert);
// 변환이 완료되면 setPreviewFile에 결과 입력
reader.onloadend = () => setPreviewFile(reader.result);
} catch(error){console.log(error)}
}
다음과 같이 구현하였다.
보이지 않는 input클릭하기
const pickImageHandler = () => {
if(!filePickerRef.current.click()){return}
filePickerRef.current.click()
}
바인드는 처음 css를 구현했던 곳에서 참고해서 하면 된다.
잘 몰라서 any가 대부분
import { ChangeEvent, useRef, useState } from "react";
import DefaultProfile from "../../../../public/icon/profileForm.png";
import styled from "styled-components";
import Image from "next/image";
import imageCompression from "browser-image-compression";
interface IProps {
profileImg?: any;
}
const ImageBox = styled.div`
width: 12rem;
height: 12rem;
position: relative;
border-radius: 50%;
margin-bottom: 4rem;
`;
const DefaultImage = styled(Image)`
cursor: pointer;
border-radius: 50%;
`;
const ImageUpload = ({ profileImg }: IProps) => {
const filePickerRef = useRef<any>();
// api요청 데이터
const [file, setFile] = useState<any>(profileImg || null);
// 미리보기 데이터
const [previewFile, setPreviewFile] = useState<any>(profileImg || null);
const pickedHandler = async (event: ChangeEvent<HTMLInputElement>) => {
if (!event.currentTarget.files) {
return;
}
const imageFile = event.currentTarget.files[0];
console.log(imageFile);
const options = {
maxSizeMB: 0.5,
maxWidthOrHeight: 120,
useWebWorker: true,
};
try {
// blob 데이터로 변환 및 압축
const compressedFile = await imageCompression(imageFile, options);
// blob에서 file로 변환
const convert = new File([compressedFile], imageFile.name, {
type: `${imageFile.type}`,
});
// api로 요청할 데이터
console.log(convert, "convert");
setFile(convert);
// preview 만들어줄 데이터
const reader = new FileReader();
reader.readAsDataURL(convert);
reader.onloadend = () => setPreviewFile(reader.result);
} catch (error) {
console.log(error);
}
};
// console.log(previewFile);
const pickImageHandler = () => {
if (!filePickerRef.current.click()) {
return;
}
filePickerRef.current.click();
};
return (
<div>
{previewFile ? (
<ImageBox>
<DefaultImage
src={previewFile}
alt="프로필"
onClick={pickImageHandler}
fill={true}
/>
</ImageBox>
) : (
<ImageBox>
<DefaultImage
src={DefaultProfile}
alt="기본"
onClick={pickImageHandler}
fill={true}
/>
</ImageBox>
)}
<input
ref={filePickerRef}
type="file"
accept=".jpg,.png,.jpeg"
onChange={pickedHandler}
style={{ display: "none" }}
/>
</div>
);
};
export default ImageUpload;