# 이미지 업로드 & 압축

어느 웹에서나 모두 쓰이는 이미지 업로드에 대해 알아보겠습니다!

# 목표

첫번째, 이미지 보낼 때는 데이터 손상을 박기 위해 바이너리파일을 formData에 넣어서 보냅니다
두번째, 저희 서버에서는 이미지의 용량을 최대 2014kb로 제한 하기 때문에, 프론트에서 2MB로 이미지 압축하는 작업을 하였습니다.

# 이미지 가져오기

  • 저는 antd-mobileImagePicker를 사용하였습니다.
  • 각 사용하시는 라이브러리에서 이미지를 가져오시면 아래와 같은 데이터 포맷을 가질 것입니다.
{
  lastModified: 1594690440635
  lastModifiedDate: Tue Jul 14 2020 10:34:00 GMT+0900 (대한민국 표준시) {}
  name: "sheep-4772994_1920.jpg"
  size: 378188
  type: "image/jpeg"
  webkitRelativePath: ""
}
  • 위에 보시면 size가 3MB가 넘죠? 일단 이것을 먼저 2MB 이하로 압축합니다.

# 이미지 압축

  • 저는 browser-image-compression라는 라이브러리를 사용하였습니다.
yarn add browser-image-compression
  • 아래 코드처럼 이미지를 가져온 후 압축합니다.
import React, { useState } from 'react';
import ImagePicker from 'antd-mobile/lib/image-picker';
import imageCompression from "browser-image-compression";

const ChangeNameAvatar = () => {
  const [data, setData] = useState({
    avatar: [],
  });

  const onClickImageUpload = (files, type, index) => {
    setData({
      ...data,
      avatar: files,
    });
  };

  const onSubmit = async () => {
    actionImgCompress(data.avatar[0].file);
  };

  const actionImgCompress = async (fileSrc) => {
    console.log("압축 시작");

    const options = {
      maxSizeMB: 0.2,
      maxWidthOrHeight: 1920,
      useWebWorker: true,
    };
    try {
      // 압축 결과
      const compressedFile = await imageCompression(fileSrc, options);

      // const reader = new FileReader();
      // reader.readAsDataURL(compressedFile);
      // reader.onloadend = () => {
      //   const base64data = reader.result;
      //   imageHandling(base64data);
      // };
    } catch (error) {
      console.log(error);
    }
  };

  return (
    <ImagePicker
      files={data.avatar}
      onChange={onClickImageUpload}
      onImageClick={(index, fs) => console.log(index, fs)}
      accept="image/gif,image/jpeg,image/jpg,image/png"
    />
    <PlainButton type="ghost" onClick={onSubmit}>
      저장하기
    </PlainButton>
  );
}
  • 라이브러리가 잘 되어있어서 압축하는 과정은 매우 쉽습니다.
  • 옵션 값을 정해주고 이미지를 넣고 실행한 해주면 됩니다.
  • 결과값은 아래와 같습니다.
Blob {
  lastModified: 1594690440635
  name: "sheep-4772994_1920.jpg"
  size: 158816
  type: "image/jpeg"
}
  • Blob(블랍)을 기억하세요!
  • size가 2MB이하로 줄어든 것을 확인할 수 있습니다.

# Base64 변환

  • 이미지 압축을 하였으니, 바이너리 파일를 text로 변환해봅시다.
  • 위에 있는 코드의 actionImgCompress 함수에서 실행합니다.

# Base64로 변환 하는 이유

  • ASCII는 데이터를 전달하기에 안전하지가 않습니다. 특수문자를 제외한 64개의 안전한 출력 문자로 서버로 전송 및 디비 저장을 위해 base64로 변환합니다.

    • 즉, 이미지 깨짐, 동영상 짤림 방지하기 위해 안전한 문자로 바꿔서 서버로 보내기 위해 변환 합니다.
  • 변환 과정도 매우 쉽습니다.

  • FileReader라는 객체를 가져와서 위에서 만든 Blob 객체를 이용해 파일의 내용을 읽고 FileReader내부의 readAsDataUrl를 이용해 base64로 변환합니다.

  • FileReader, readAsDataUrl 여기 링크에 자세한 내용이 있습니다. 참고해주세요.

const actionImgCompress = async fileSrc => {
  console.log("압축 시작");

  const options = {
    maxSizeMB: 0.2,
    maxWidthOrHeight: 1920,
    useWebWorker: true
  };
  try {
    const compressedFile = await imageCompression(fileSrc, options);

    // FileReader 는 File 혹은 Blob 객체를 이용하여, 파일의 내용을 읽을 수 있게 해주는 Web API
    const reader = new FileReader();
    reader.readAsDataURL(compressedFile);
    reader.onloadend = () => {
      // 변환 완료!
      const base64data = reader.result;

      // formData 만드는 함수
      handlingDataForm(base64data);
    };
  } catch (error) {
    console.log(error);
  }
};

# formData 핸들링

  • 변환을 완료했으면, 이제 서버 전송전에 formData를 만들어 이미지 및 다른 정보를 보낼 준비를 합니다.
  • 여기서 Blob를 핸들링 해야합니다.

# Blob

  • JavaScript에서 Blob(Binary Large Object, 블랍)은 이미지, 사운드, 비디오와 같은 멀티미디어 데이터를 다룰 때 사용할 수 있습니다.
  • 대개 데이터의 크기(Byte) 및 MIME 타입을 알아내거나, 데이터를 송수신을 위한 작은 Blob 객체로 나누는 등의 작업에 사용합니다.
  • Blob 사용하기 추가로 더 공부하고 싶은 분들은 여기를 참고해주세요.

# 다시 formData를 만들어봅시다

const handlingDataForm = async dataURI => {
  // dataURL 값이 data:image/jpeg:base64,~~~~~~~ 이므로 ','를 기점으로 잘라서 ~~~~~인 부분만 다시 인코딩
  const byteString = atob(dataURI.split(",")[1]);

  // Blob를 구성하기 위한 준비, 이 내용은 저도 잘 이해가 안가서 기술하지 않았습니다.
  const ab = new ArrayBuffer(byteString.length);
  const ia = new Uint8Array(ab);
  for (let i = 0; i < byteString.length; i++) {
    ia[i] = byteString.charCodeAt(i);
  }
  const blob = new Blob([ia], {
    type: "image/jpeg"
  });
  const file = new File([blob], "image.jpg");

  // 위 과정을 통해 만든 image폼을 FormData에 넣어줍니다.
  // 서버에서는 이미지를 받을 때, FormData가 아니면 받지 않도록 세팅해야합니다.
  const formData = new FormData();
  formData.append("representative_avatar", file);

  // 필요시 더 추가합니다.
  formData.append("name", "nkh");
  formData.append("email", "noh5524@gmail.com");

  try {
    const changeAvatar = await apis.auth.changeUserAccount(formData);
    alert(changeAvatar.status);
  } catch (error) {
    alert(error.response.data.errors);
  }
};

# axios 전송 전 request 설정

  • 데이터를 보낼 준비를 마쳤다면, axios의 header부분에 내가 보내는 타입이 이미지도 있고, 텍스트도 있는 여러 데이터 폼을 보낸다고 알려야해요.
// 회원 정보 변경
import axios from "../axios";
export default {
  changeUserAccount: datas => {
    // request의 header부분에 아래와 같이 타입을 설정해줍니다.
    const headers = {
      "Content-Type": "multipart/form-data"
    };

    return axios.post(`/me`, datas, { headers });
  }
};

# 끝!

Last Updated: 3/24/2021, 8:55:12 PM