티스토리 뷰

반응형

이미지를 첨부하고, 첨부한 이미지를 브라우저 상에서 보여주는 방식을 알아보자. 브라우저에서 이미지를 업로드하여 보여주는 방식으로는 fileReader를 사용하는 방식과 URL.createObjectURL()을 사용하는 방식이 있다. 오늘 알아볼 방식은 URL.createObjectURL()이다.  우선은 작동 순서를 알아보고, React로 전반적인 파일 업로드와 Image를 브라우저로 표현하는 방식에 대해 알아보자. 그 후에 vanillaJS로 URL.revokeObjectURL() 도 추가로 알아보도록 하자.

 

 

 


 

구현 로직 순서

우리가 구현할 로직을 우선 생각해 보면 아래와 같다.

  1. input 태그를 통해 image를 받는다.
  2. 받은 image 파일에서 Blob 객체를 뽑아낸다. 
  3. 뽑아낸 Blob 객체를 URL.createObjectURL 메서드를 통해 img 객체의 src에 넣을 수 있는 DOMString으로 변환한다.
  4. img 태그를 생성하여 넣어준다.
  5. 자유롭게 사용한다.

그렇다면 Blob란 무었일까?

 

 


 

Blob란

우리는 이미지를 Blob 형태로 변경을 한다. Blob는 Binary Large Object의 줄임말로 이미지나 사운드 파일 같은 큰 데이터를 다룰 때 사용한다. 이러한 멀티미디어 객체를 저장하기 위해 주로 사용하는 Blob는 javascript에서도 이미지/비디오/사운드를 핸들링할 때 많이 사용한다고 보면 된다. 추가적인 특징으로는 File 객체는 Blob를 확장하여 만든 것이므로, Blob가 사용가능한 곳에는 File 객체도 사용가능하다. Blob 자체를 DB에 저장하여 사용하기도 하는데, 모든 DBMS에서 사용가능한 것은 아니기 때문에 DB 전략을 만들기 전에 사용가능한지에 대한 확인이 필요하다.

 

 


 

 

URL.createObjectURL()

URL.createObjectURL() 메서드는 input으로 들어온 객체를 가리키는 URL을 DOMString으로 변환하는 기능을 한다. 이때 URL 자체는 브라우저 메모리에 올라가기 때문에 창을 닫으면 사라진다. 이때, URL.createObjectURL(object)에서 object로 들어가는 값은 File, Blob, MediaSource 객체들이 가능하다.

 

component 안에서 react로 구현을 해보자.

 

 

1. useState 상태값 정하기

 

const [image, setImage] = useState();

 

image 태그 안에 src에 들어간 부분을 image로 상태를 지정한다. 이 로직에는 URL.createObjectURL()의 출력값이 들어간다.

 

 

 

2. 파일 업로드 시 image 상태에 값을 넣을 handle 함수 만들기

 

  const handleImageChange = (e) => {
    if (!e.target.files) return;
    const file = e.target.files[0];
    if (file) {
      let image = window.URL.createObjectURL(file);
      setImage(image);
    }
  };

 

위의 handleImageChange 함수는 input 태그에 이미지를 첨부했을 때, 첨부된 이미지에서 Blob 값을 빼서 DOMString으로 변환하는 로직까지 포함되어 있다. 이때, e.target.files는 input 태그를 통해 넣은 파일들이 들어간다. 우리는 하나의 image만 첨부했다고 가정을 했기 때문에 e.target.files[0]으로 file을 변수에 담는다. 이때, file이 우리가 실제로 말하는 파일값으로 백엔드로 보내는 로직이 필요하다면, 이 파일 자체를 보내면 된다. e.target.files[0] 을 console을 찍어보면, 아래와 같은 결과를 확인할 수 있다.

 

 

위의 형식으로 console이 찍히는 것이 바로 File 형식인 것이다. URL.createObjectURL 결과 값은 아래와 같다.

 

 

이 값을 우리는 image 상태값에 대입하고 <img src={image} />와 같이 넣어서 이미지를 보이도록 만들면 된다.

 

 

3. 컴포넌트 내부 JSX return 부분

 

  return (
    <>
      <div>
        <label htmlFor="testImage">이미지 추가</label>
        <input
          id="testImage"
          type="file"
          accept="image/*"
          onChange={handleImageChange}
        />
      </div>
      <div>
        {image && (
          <Image
            src={image}
            fill
            alt={"추가된 이미지 입니다."}
          />
        )}
      </div>
    </>
  );

 

위의 첫 이미지는 아래와 같다. 

 

 

label 태그 부분을 조금 살펴보면, htmlFor 속성값과 input 태그의 id 값을 동일하게 적어야 한다. 이렇게 작성을 해야 이미지 변경 글자를 눌러도 input 태그의 type에 관련된 action들이 실행된다. 그리고 버튼을 눌러 이미지를 추가하면, 추가된 이미지가 브라우저에서 보이게 된다. 사실 익숙하지 않아서 그렇지 그렇게 어려운 내용들은 없다고 할 수 있다.

 

 

만약 브라우저에서 보이고 있는 이미지를 메모리에서 삭제하고 싶다면 아래의 함수를 작성해서 사용하면 된다.

 

// img 메모리 제거
const deleteFileImage = () => {
  URL.revokeObjectURL(image);
  setImage("");
};

 

변환한 URL을 저장했던 image 초기화해주는 로직이다. 여기서 URL을 초기화하기 위해 사용되고 있는 URL.revokeObjectURL 메서드는 무엇일까?

 

 


 

URL.revokeObjectURL()

URL.revokeObjectURL()은 createObjectURL을 통해 생성된 Url을 메모리에서 지울 때 사용한다. 굳이 폐기하는 이유는 javascript 엔진 자체는 생성된 url을 계속 사용하고 있다고 판단을 한다. 따라서 GC로 처리하지 않아 불필요한 메모리 영역이 할당되어 유지되는 것이다. 따라서 URL.revokeObjectURL을 통해서 생성한 Url을 해제하여야 불필요한 메모리 누수를 막을 수 있다.

 

1. Vanilla JS로 이미지 표시하기

 

우선 화면을 그리는 HTML을 아래와 같이 만들어 보자.

// upload.html
<html lang="ko">
  <head>
    <meta charset="utf-8" />
    <title></title>
  </head>
  <body>
    <input
      type="file"
      id="fileElem"
      multiple
      accept="image/*"
      style="display: none"
      onchange="handleFiles(this.files)"
    />
    <a href="#" id="fileSelect">Select some files</a>
    <div id="fileList">
      <p>No files selected!</p>
    </div>
    <script src="./upload.js"></script>
  </body>
</html>

 

만들고자 하는 로직은, input 태그의 type을 file로 설정하면 첫 번째에서 위 실습에서 확인한 것처럼 Choose File 버튼이 생성된다. 그러나 Input 창 말고 다른 UI를 적용하는 방식을 적용하기 위해, input UI는 숨기고 a 태그를 추가로 넣어주었다. 그래서 a 태그를 누르면 input 태그가 실행되는 방식으로 javascript에서 추가를 해줄 것이다. id="fileList" div 태그 부분은 추가한 파일의 이미지를 뿌려주는 부분이다. 이때 javascript에서 추가할 로직은 하나를 넣으면 하나의 이미지가 생성되고, 여러 개의 이미지를 한꺼번에 넣으면 여러개의 이미지가 동시에 브라우저에 표출되도록 구현해 보자.

 

HTML만 보면 아래와 같은 화면이 나올 것이다.

 

 

다음은 HTML에 활력을 넣어줄 JavaScript를 만들어 보자.

 

window.URL = window.URL || window.webkitURL;

const fileSelect = document.getElementById("fileSelect");
const fileElem = document.getElementById("fileElem");
const fileList = document.getElementById("fileList");

fileSelect.addEventListener(
  "click",
  function (e) {
    if (fileElem) {
      fileElem.click();
    }
    e.preventDefault(); // # 이동 방지
  },
  false
);

function handleFiles(files) {
  if (!files.length) {
    fileList.innerHTML = "<p>No files selected!</p>";
  } else {
    fileList.innerHTML = "";
    const list = document.createElement("ul");
    fileList.appendChild(list);
    for (let i = 0; i < files.length; i++) {
      const li = document.createElement("li");
      list.appendChild(li);

      const img = document.createElement("img");
      img.src = window.URL.createObjectURL(files[i]);
      img.height = 60;
      img.onload = function () {
        window.URL.revokeObjectURL(this.src);
      };
      li.appendChild(img);
      const info = document.createElement("span");
      info.innerHTML = files[i].name + ": " + files[i].size + " bytes";
      li.appendChild(info);
    }
  }
}

 

우선은 URL 메서드를 사용하기 위해 크로스 브라우저를 대비한 window.URL와 window.webkitURL로 window.URL 변수를 만들어 준다. fileSelect 변수에 지정한 이벤트리스너를 보면, 클릭 시 input 태그를 클릭한 것과 동일하게 작동하도록 로직이 작성되어 있다. 그리고 e.preventDefault() 부분은 기본적으로 a 태그의 작동 방식인 페이지 이동을 방지하기 위한 로직이다.

 

handleFiles() 함수에서 UI 생성로직과 파일변환로직이 진행된다. core 로직만 확인을 해보면, input의 onchange 로직으로 파일 첨부 시 files가 추가된다. URL.createObjectURL을 통해 URL을 img 태그의 src에 넣어준다. 그리고 Img가 로딩이 완료되면, URL.revokeObjectURL로 URL을 해제하여 메모리 누수를 막는다.

 

 

반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함