Input type=file 이미지 업로드 - Input type=file imiji eoblodeu

input 태그에서 선택한 이미지를 화면에 바로 출력하도록 해보자.

코드

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>썸네일 만들기</title>
  </head>
  <body>
    <input type="file" id="image" accept="image/*" onchange="setThumbnail(event);"/>
    <div id="image_container"></div>

    <script>
      function setThumbnail(event) {
        var reader = new FileReader();

        reader.onload = function(event) {
          var img = document.createElement("img");
          img.setAttribute("src", event.target.result);
          document.querySelector("div#image_container").appendChild(img);
        };

        reader.readAsDataURL(event.target.files[0]);
      }
    </script>
  </body>
</html>

input 태그에서 onchange 속성에 실행될 메소드를 정해줍니다.

메소드 내에서 FileReader 객체를 생성하고 onload 됐을 시 발생할 이벤트를 작성해줍니다.
FileReader 가 로드 됐을 때 img 엘리먼트를 생성하고 src 속성을 설정해줍니다.
그리고 이미지가 들어갈 div에 넣어줍니다.

리더에서 input 태그에서 선택한 파일을 읽어오도록 설정합니다.

실행 화면

Input type=file 이미지 업로드 - Input type=file imiji eoblodeu

여러 이미지 미리보기

    <!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>썸네일 만들기</title>
  </head>
  <body>
    <input type="file" id="image" accept="image/*" onchange="setThumbnail(event);" multiple/>
    <div id="image_container"></div>

    <script>
      function setThumbnail(event) {
        for (var image of event.target.files) {
          var reader = new FileReader();

          reader.onload = function(event) {
            var img = document.createElement("img");
            img.setAttribute("src", event.target.result);
            document.querySelector("div#image_container").appendChild(img);
          };

          console.log(image);
          reader.readAsDataURL(image);
        }
      }
    </script>
  </body>
</html>

input 태그에 multiple 속성을 추가해주었고
setTumbnail 메소드 안에서 반복문을 돌려서 미리보기를 설정합니다.


자바스크립트에서는 input type="file" 태그를 이용하여 이미지를 포함한 다양한 확장자의 파일을 업로드할 수 있다.

만약 이미지만 업로드하게끔 제한하고 싶다면 accept="image/*" 프로퍼티를 추가하면 된다.

이와 비슷하게 비디오 파일만 업로드하고 싶다면 accept="video/*", 오디오 파일만 업로드하고 싶다면 accept="audio/*" 이렇게 사용하면 된다.

혹은 특정 확장자만 허용하는 방법도 있다.

예를 들어 아래와 같이 작성하면 png, jpeg 확장자를 갖는 이미지만 업로드가 가능하다.

// png, jpeg 확장자를 갖는 이미지만 업로드 허용
<input type="file" accept="image/png, image/jpeg">
// 혹은
<input type="file" accept=".png, .jpeg">

이제 업로드한 이미지 미리보기를 구현하는 방법을 알아보자.

미리보기를 구현하기 위해 널리 사용되는 두 가지 방법으로는 FileReader를 이용하는 방식과 URL API를 이용한 방식이 있다.

FileReader를 이용한 방법

FileReader 객체의 readAsDataURL 메소드를 이용하여 인자로 전달받은 File 객체를 base64 형태의 문자열로 변환한다.

변환된 문자열은 img 태그의 src 혹은 다른 HTML 태그의 background-image url 값으로 사용할 수 있다.

Input type=file 이미지 업로드 - Input type=file imiji eoblodeu
base64 문자열로 변환된 이미지 파일

사용 방법은 다음과 같다.

const fileDOM = document.querySelector('#file');
const preview = document.querySelector('.image-box');

fileDOM.addEventListener('change', () => {
  const reader = new FileReader();
  reader.onload = ({ target }) => {
    preview.src = target.result;
  };
  reader.readAsDataURL(fileDOM.files[0]);
});

주의할 점으로는 FileReader가 비동기적으로 동작한다는 점이다.

(동기적으로 동작하는 FileReaderSync도 있다.)

따라서, reader에 파일 객체가 load된 이후에 미리보기 영역의 src 값이 변경되도록 onload 이벤트로 처리해야 한다.

URL API를 이용한 이미지 URL 생성

이 방식은 업로드한 이미지를 브라우저 메모리에 저장하여 이미지 URL을 생성하는 방식이다.

Input type=file 이미지 업로드 - Input type=file imiji eoblodeu
URL API로 생성된 이미지 URL

사용 방법은 다음과 같다.

const fileDOM = document.querySelector('#file');
const preview = document.querySelector('.image-box');

fileDOM.addEventListener('change', () => {
  const imageSrc = URL.createObjectURL(fileDOM.files[0]);
  preview.src = imageSrc;
});

- URL 생성

createObjectURLFile 혹은 blob 형태의 데이터 객체, MediaSource 객체를 인자로 전달하여 사용하면 된다.

const fileDOM = document.querySelector('#file');

// 링크 생성
const src = URL.createObjectURL(fileDOM.files[0]);  // blob 형태의 이미지 파일

- URL 삭제

revokeObjectURL생성된 이미지 URL을 전달하여 사용하면 된다.

// 링크 삭제
URL.revokeObjectURL(src);  // string 타입

이 방법을 사용할 때는 메모리 누수를 주의해야 한다.

동일한 파일 객체를 이용하여 URL을 생성한다고 해도 새로운 URL을 생성한다.

사용하지 않는 이미지 URL의 경우에는 반드시 revokeObjectURL을 사용하여 메모리에서 해제하자.

(물론 브라우저를 종료하면 생성한 URL도 함께 메모리에서 해제된다.)

두 방식 비교

FileReader를 이용하는 방식은 이미지를 base64 데이터로 변환하여 사용하고, 해당 페이지(document)가 종료될 때까지 페이지의 메모리를 차지한다.

변환된 base64 문자열은 다른 사이트에서도 사용이 가능하다는 장점이 있지만, 다음과 같은 단점도 존재한다.

- 변환된 문자열 길이가 굉장히 길기 때문에 마크업의 가독성이 떨어진다.

- 이미지 파일의 크기가 클 수록 문자열 자체를 화면에 렌더링하는 시간이 많이 소요된다. 

URL API를 이용하는 방식도 마찬가지로 해당 페이지 종료 시(SPA의 경우에는 다른 사이트로 이동하거나 탭을 종료 시)까지 메모리를 차지하지만, 직접 메모리에서 해제가 가능하고 사용하지 않는 URL의 경우에는 가비지 콜렉터에서 메모리를 수집하기 때문에 좀 더 효율적인 메모리 관리가 가능하다.

또한, FileReader는 비동기적으로 동작하고 URL API는 동기적으로 동작하므로 무거운 작업을 수행 중일 때 FileReader를 사용하게 되면 기능 동작이 느려질 수 있다.

마무리!

예제 및 코드는 아래 codepen을 참고하면 된다.

See the Pen Line Graph by MiJeong Kim (@sap03110) on CodePen.

참고 자료

URL.createObjectURL() - Web API | MDN

URL.createObjectURL() 정적 메서드는 주어진 객체를 가리키는 URL을 DOMString으로 반환합니다.

developer.mozilla.org

Input type=file 이미지 업로드 - Input type=file imiji eoblodeu

URL.revokeObjectURL() - Web API | MDN

URL.revokeObjectURL() 정적 메서드는 이전에 URL.createObjectURL()을 통해 생성한 객체 URL을 해제합니다.

developer.mozilla.org

Input type=file 이미지 업로드 - Input type=file imiji eoblodeu