-
[Javascript] 이미지 리사이징으로 용량 줄이기 (Canvas API)Java Script 2024. 3. 27. 13:43
최근에 대한민국 철봉 지도라는 프로젝트를 진행하면서
사용자가 지도에 철봉 위치를 등록하면서 주변 사진을 이미지로 업로드하고,
업로드한 이미지를 다른 사용자들이 확인할 수 있는 기능을 구현하였습니다.이때 저희 서비스 특성상 고화질 이미지를 제공할 필요성이 크지 않고,
거리뷰를 통해 확인할 수 있는 기능을 제공함으로써 서버의 부담을 줄이고, 로딩 속도를 올리기 위해
이미지를 리사이징 하여 용량을 줄이기로 결정하였습니다.
| react-image-file-resizer
먼저 이미지를 리사이징 react-image-file-resizer라는 라이브러리를 활용해 보기로 했습니다.
https://www.npmjs.com/package/react-image-file-resizer
해당 라이브러리를 사용하면 간단하게 아래 코드와 같이 이미지를 리사이징 할 수 있습니다.
import Resizer from "react-image-file-resizer"; const resizeFile = (file) => new Promise((resolve) => { Resizer.imageFileResizer( file, 300, // 리사이징 적용할 최대 넓이 300, // 리사이징 적용할 최대 높이 "JPEG", // 리사이징 이미지 포맷 100, // 리사이징 품질 0, // 이미지 회전 정도 (uri) => { resolve(uri); // 리사이징 된 이미지 URI }, "base64" // 출력 타입 ); });
그리고 해당 함수를 이미지가 업로드될 때 다음과 같이 적용하면 됩니다.
const onChange = async (event) => { try { const file = event.target.files[0]; const image = await resizeFile(file); console.log(image); } catch (err) { console.log(err); } };
다만 지금 프로젝트에서는 최대 넓이와 높이를 정해서 리사이징 하는 것이 아닌,
현재 이미지 크기의 80%로 줄이는 방식으로 구현하고 싶었습니다.
해당 라이브러리에는 이러한 기능이 없는 거 같아서 직접 구현해 보기로 결정했습니다.
| Canvas API를 통한 이미지 리사이징
먼저 축소하고자 할 비율과 파일을 받아 Promse를 리턴하는 resizeFile함수를 만들어 줍니다.
const resizeFile = async (file: File, scale: number): Promise<File> => { return new Promise((resolve, reject) => { ... }); }; export default resizeFile;
이제 리턴하는 해당 프로미스의 콜백함수 안에서
Canvas API를 사용하여 이미지를 축소하고 내보내면 됩니다.const resizeFile = async (file: File, scale: number): Promise<File> => { return new Promise((resolve, reject) => { // 파일의 내용을 읽을 수 있도록 파일 리더 객체를 생성합니다. const reader = new FileReader(); reader.onload = (e) => { // 새 이미지 객체를 생성합니다. const img = new Image(); img.onload = () => { // 원하는 스케일을 적용하여 새로운 너비와 높이를 계산합니다. const width = img.width * scale; const height = img.height * scale; // 캔버스를 생성하고 크기를 설정합니다. const canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; // 캔버스의 2D 컨텍스트를 가져옵니다. const ctx = canvas.getContext("2d"); // 컨텍스트를 사용하여 새로 계산된 크기로 이미지를 조정합니다. ctx?.drawImage(img, 0, 0, width, height); // 캔버스의 내용을 바탕으로 파일로 변환할 Blob 객체를 생성합니다. canvas.toBlob((blob) => { if (blob) { // 새 파일 객체를 생성합니다. 파일 이름과 유형은 원본 파일에서 가져옵니다. const resizedFile = new File([blob], file.name, { type: file.type, lastModified: Date.now(), }); // 생성된 파일 객체를 반환합니다. resolve(resizedFile); } else { reject(new Error("실패")); } }, file.type); }; // FileReader 객체가 로드한 파일 데이터를 이미지 소스로 설정합니다. img.src = e.target?.result as string; }; // FileReader에게 파일의 내용을 Data URL 형태로 읽도록 요청합니다. reader.readAsDataURL(file); }); }; export default resizeFile;
이제 해당 함수를 사용하여 이미지를 업로드하고,
파일 형식을 확인하여 미리 보기를 보여주는 기능을 아래와 같이 만들 수 있습니다.const handleImageChange = async (e: ChangeEvent<HTMLInputElement>) => { const suppertedFormats = [ "image/jpeg", "image/png", "image/svg+xml", "image/webp", ]; if (!e.target.files) return; if (!suppertedFormats.includes(e.target.files[0]?.type)) { setErrorMessage( "지원되지 않은 이미지 형식입니다. JPEG, PNG, webp형식의 이미지를 업로드해주세요." ); return; } if (images.length + e.target.files.length > 5) { setErrorMessage("최대 5개 까지 등록 가능합니다!"); return; } let file: File = await resizeFile(e.target.files[0], 0.8); let reader = new FileReader(); reader.onloadend = () => { const imageData = { file: file, previewURL: reader.result as string, id: nanoid(), }; setImages((prev) => [...prev, imageData]); formState.setImageForm(imageData); }; if (file.size / (1024 * 1024) > 10) { setErrorMessage("이미지는 최대 10MB까지 가능합니다."); return; } setErrorMessage(""); reader.readAsDataURL(file); };
| 결과
확실히 이미지의 크기를 줄이면 로딩 속도가 눈에 보이게 차이가 납니다.
또한 용량이 줄어들기 때문에 서버의 부담도 줄일 수 있어 좋은 거 같습니다.
'Java Script' 카테고리의 다른 글
[Javascript] (User Agent) 사용자가 접속한 브라우저, 기기 확인하기 (0) 2024.06.11 [Javascript] 이벤트 버블링(Event Bubbling), 이벤트 위임(Event Delegation) (0) 2024.03.07 [Javascript] 단축 평가(short circuit evaluation) (1) 2024.02.16 [Java Script] for과 forEach의 차이 및 배열의 비동기 작업 (0) 2023.08.28 [Java Script] 클로저(Closure) (0) 2023.08.19