자바스크립트 이미지 리사이징

🖼이미지 리사이징🖼

이번에 보게된 이슈는 이미지 리사이징이다.

하고있는 프로젝트에서 프로필사진을 동그란모양의 중간에 올수있도록 설정을 하고 싶어서 자바스크립트로 이미지 리사이징 하는법을 알아보았다.

이미지의 실제 비율과 똑같이 유지하면서 이미지의 실제크기를 변경을 할수가 있다. 이미지를 큰사이즈를 그대로 로드 한뒤에 width, height를 조절하면 서버에 업로드 할시에 용량떄문에 서버에 무리를 주기 때문에 이미지 실제크기를 줄여서 업로드 하는법을 이용하기로 하였다.

html 코드

    <form action="">
        <input type="file" id="chooseFile" name="chooseFile" class="img-input" required>
        <button class="submit-btn">사진업로드</button>
    </form>
    <div class="img">
        
    </div>

먼저 파일을 받을수 있도록 form 안에 input은 type을 file로 만들어준다.

javascript 전체 코드

const realInput = document.querySelector(".img-input");
const imgTag = document.querySelector(".img");

const resizeImage = (settings) => {
  const file = settings.file;
  const maxSize = settings.maxSize;
  const reader = new FileReader();
  const image = new Image();
  const canvas = document.createElement("canvas");

  const dataURItoBlob = (dataURI) => {
    const bytes =
      dataURI.split(",")[0].indexOf("base64") >= 0
        ? atob(dataURI.split(",")[1])
        : unescape(dataURI.split(",")[1]);
    const mime = dataURI.split(",")[0].split(":")[1].split(";")[0];
    const max = bytes.length;
    const ia = new Uint8Array(max);
    for (let i = 0; i < max; i++) ia[i] = bytes.charCodeAt(i);
    return new Blob([ia], { type: mime });
  };

  const resize = () => {
    let width = image.width;
    let height = image.height;
    if (width > height) {
      if (width > maxSize) {
        height *= maxSize / width;
        width = maxSize;
      }
    } else {
      if (height > maxSize) {
        width *= maxSize / height;
        height = maxSize;
      }
    }
    canvas.width = width;
    canvas.height = height;
    canvas.getContext("2d").drawImage(image, 0, 0, width, height);
    const dataUrl = canvas.toDataURL("image/jpeg");
    return dataURItoBlob(dataUrl);
  };

  return new Promise((ok, no) => {
    if (!file) {
      return;
    }
    if (!file.type.match(/image.*/)) {
      no(new Error("Not an image"));
      return;
    }
    reader.onload = (readerEvent) => {
      image.onload = () => {
        return ok(resize());
      };
      image.src = readerEvent.target.result;
    };
    reader.readAsDataURL(file);
  });
};

const handleImgInput = (e) => {
  const config = {
    file: e.target.files[0],
    maxSize: 350,
  };
  resizeImage(config)
    .then((resizedImage) => {
      const url = window.URL.createObjectURL(resizedImage);
      const img = document.createElement("img");
      img.setAttribute("src", url);
      img.className = "profile-img";
      img.style.display = "block";
      imgTag.appendChild(img);
    })
    .then(() => {
      const img = document.querySelector(".profile-img");
      img.onload = () => {
        const widthDiff = (img.clientWidth - imgTag.offsetWidth) / 2;
        const heightDiff = (img.clientHeight - imgTag.offsetHeight) / 2;
        img.style.transform = `translate( -${widthDiff}px , -${heightDiff}px)`;
      };
    })
    .catch((err) => {
      console.log(err);
    });
};

realInput.addEventListener("change", handleImgInput);

여기서 canvas 태그를 이용하여 새로운 이미지사이즈를 생성시킨다. 저기서 나온 Url만으로도 사진을 줄일수 있다. 하지만 저 상태로 쓰게 된다면 실제 이미지는 아무것도 나오지 않게 되며 console.log 로 출력해볼시 img 태그의 src에 아무것도 안들어있는 것을 확인할수 있게된다.
그 이유는 뭘까? 자바스크립트 src는 너무 긴 길이를 받아버릴시 아무 이미지도 나타나게 되지않는다. 실제로 dataUrl에서 나오게 된것의 길이를 측정해보면 거의 6백만이 나오게 된다.

그러므로 사용하는 기술이 Blob이다. 이번에 이미지 리사이징을 하며 처음알게된 개념이다.
Blob이란 Binary Large Object의 약자이며 이미지,사운드,동영상 같은 대용량 바이너리 데이터를 담을수 있다.

Blob 객체로 반환한뒤에 window.URL.createObjectURL(resizedImage) 를 이용하면 Blob형태를 url형태로 반환하며 Blob:http://localhost:3000 ... 이런 식으로 나오게 된다. 이제 이것을 image의 src에 넣어주면 변경된 이미지를 사용할수 있다.

파일 받기

우리가 file을 제출해서 그것으로 이미지를 받아오는 방법은 form제출 이벤트에서 event.target.files를 conosle.log 해보면 file들을 제출하게되는 배열이 반환된다.

그리고 const reader = new FileReader(); 파일리더를 객체를 만들어서

    reader.onload = (readerEvent) => {
      image.onload = () => {
        return ok(resize());
      };
      image.src = readerEvent.target.result;
    };

파일을 불러오면 resize를 실행하며 readerEvent.target.result에는 그 이미지의 url이 반환된다.

이미지 중앙정렬 시키기

resizeImage(config)
    .then((resizedImage) => {
      const url = window.URL.createObjectURL(resizedImage);
      const img = document.createElement("img");
      img.setAttribute("src", url);
      img.className = "profile-img";
      img.style.display = "block";
      imgTag.appendChild(img);
    })
    .then(() => {
      const img = document.querySelector(".profile-img");
      img.onload = () => {
        const widthDiff = (img.clientWidth - imgTag.offsetWidth) / 2;
        const heightDiff = (img.clientHeight - imgTag.offsetHeight) / 2;
        img.style.transform = `translate( -${widthDiff}px , -${heightDiff}px)`;
      };
    })
    .catch((err) => {
      console.log(err);
    });

위 코드의 뒷부분에 있는 코드이다. 그냥 일반적으로 사용하게 된다면

이런식으로 사진이 div태그와 왼쪽 끝이 겹치게 된다.

하지만 우리가 원하는 이미지 상태는 이것이기 때문에 수식으로 생각하면 쉽다.
실제 img의 크기에서 프로필 틀의 크기를 뺸것을 절반으로 나눈만큼 이미지를 이동시킨다면 어떤 이미지든 중앙에 위치하게 된다.

그런데 처음에 이 코드를 구현할떄 아무리 img.clientWidth를 구해도 width,height 모두 0이 나와서 꽤나 시간을 썻었다. 그 이유는 img가 src를 받고 load할떄까지의 텀이 있는데 일반적으로 코드를 사용하게 되면 load가 되기전의 그 이미지를 받아서 아무것도 없는 이미지의 width,height를 가져오게 되어서 0이 나오는 것이다.

그래서 img.onload = ... 을 이용해서 load가 되면 그때 width,height를 측정하여 translate를 시켜주면 우리가 원하는 모양이 된다.

✨추가

마지막으로 저 이미지를 동그란 형태안에 넣는 방법이다. img태그위에 div태그로 감싸준 다음에
div 태그에 다음과 같은 스타일을 넣어주면 된다.

.img {
  width: 150px;
  height: 150px;
  border-radius: 50%;
  overflow: hidden;
}

overflow: hidden 을 하게 되면 div 태그 밖으로 나가는 이미지들은 저절로 잘리는 이미지를 하게된다.

좋은 웹페이지 즐겨찾기