사용자가 서버로 보내는 이미지의 용량을 줄여서 서버의 용량도 절약해주고, 사용자가 이미지를 불러올 때 가볍게 불러올 수 있도록 하려고 했습니다. 그래서 사용자가 인풋에서 이미지를 업로드 할 때 webp로 변환하는 기능을 구현하게 되었습니다
https://www.npmjs.com/package/browser-image-compression
다운로드 수 : 137,399
번들 사이즈 : 55.51KB
마지막 업데이트 : 7개월 전
https://www.npmjs.com/package/upload-images-converter
다운로드 수 : 31
번들 사이즈 : 5.05KB
마지막 업데이트 : 2개월 전
import { imageConverter } from 'upload-images-converter';
const compressedBlob = await imageConverter({ files: [imageFile], width: 1280, height: 1280 });
이미지를 webp로 변환하는 기능만 필요하고, 두 패키지의 번들 사이즈가 11배 차이가 나다보니 더 가벼운 패키지인 Upload Images Converter을 사용하기로 했습니다.
다만 Browser Image Compression에서 지원하는 기능 중 자동으로 사진 비율대로 사진 크기를 잘라서 반환하는 기능을 Upload Images Converter에서 지원하지 않기 때문에 추가로 코드를 작성하여서 구현했습니다
Upload Images Converter는 가로, 세로를 직접 지정해서 사용하는 방법만 제공해주고 있었습니다. 그래서 이미지의 사이즈를 구하고, 직접 가로 세로 값을 계산하여서 가로, 세로 값을 입력해 webp 사진을 반환받으려고 했습니다.
getImageSize.ts
export const getImageSize = (imageFile: File): Promise<{ width: number; height: number }> => {
return new Promise((resolve, reject) => {
const img = new Image();
const reader = new FileReader();
img.onload = () => {
const width = img.naturalWidth;
const height = img.naturalHeight;
resolve({ width, height });
};
reader.onload = () => {
img.src = reader.result?.toString() ?? '';
};
reader.readAsDataURL(imageFile);
img.onerror = error => {
reject(error);
};
reader.onerror = error => {
reject(error);
};
});
};
비율의 사이즈를 구하는 공식은 작은값 * 최대값 / 큰값 으로 하였습니다.
공식의 원리는 이해하지 못했습니다만 공식을 활용하여 계산했습니다
calculateAspectRatioSize.ts
export const calculateAspectRatioSize = ({
originWidth,
originHeight,
maxWidthOrHeight,
}: {
originWidth: number;
originHeight: number;
maxWidthOrHeight: number;
}) => {
const maxSize = Math.max(originWidth, originHeight);
if (maxSize <= maxWidthOrHeight) {
return { width: originWidth, height: originHeight };
}
if (originWidth === maxSize) {
const width = maxWidthOrHeight;
const height = Number(((originHeight * maxWidthOrHeight) / originWidth).toFixed(1));
return { width, height };
}
const width = Number(((originWidth * maxWidthOrHeight) / originHeight).toFixed(1));
const height = maxWidthOrHeight;
return { width, height };
};
calculateAspectRatioSize.test.ts
import { calculateAspectRatioSize } from '@utils/image/calculateAspectRatioSize';
test.each([
[
{ originWidth: 3000, originHeight: 820, maxWidthOrHeight: 1280 },
{ width: 1280, height: 349.9 },
],
[
{ originWidth: 1280, originHeight: 1303, maxWidthOrHeight: 1280 },
{ width: 1257.4, height: 1280 },
],
[
{ originWidth: 2403, originHeight: 603, maxWidthOrHeight: 1280 },
{ width: 1280, height: 321.2 },
],
[
{ originWidth: 1200, originHeight: 820, maxWidthOrHeight: 1280 },
{ width: 1200, height: 820 },
],
[
{ originWidth: 200, originHeight: 200, maxWidthOrHeight: 1280 },
{ width: 200, height: 200 },
],
])(
'calculateAspectRatioSize에서 입력받은 너비, 높이가 `maxWidthOrHeight`보다 크다면, 너비를 `maxWidthOrHeight`로 설정하고, 높이를 원래의 가로 세로 비율을 유지하며 계산하여 반환합니다. 너비, 높이 둘 중 긴 값이 최대 너비 혹은 높이보다 작다면 기존의 너비, 높이를 반환합니다.',
({ originWidth, originHeight, maxWidthOrHeight }, expected) => {
const result = calculateAspectRatioSize({ originWidth, originHeight, maxWidthOrHeight });
expect(result).toEqual(expected);
}
);
compressedBlob = [File]
outputWebpFile = File
dataTransfer.files = FileList
convertImageToWebP.ts
import { imageConverter } from 'upload-images-converter';
import { calculateAspectRatioSize } from './calculateAspectRatioSize';
import { getImageSize } from './getImageSize';
export const convertImageToWebP = async (imageFile: File) => {
const { width: originWidth, height: originHeight } = await getImageSize(imageFile);
const { width, height } = calculateAspectRatioSize({
originWidth,
originHeight,
maxWidthOrHeight: 1280,
});
const compressedBlob = await imageConverter({
files: [imageFile],
width,
height,
});
const outputWebpFile = new File([compressedBlob[0]], `${Date.now().toString()}.webp`);
const dataTransfer = new DataTransfer();
dataTransfer.items.add(outputWebpFile);
return dataTransfer.files;
};
const webpFileList = await convertImageToWebP(imageFile);
inputElement.files = webpFileList;