프로필

프로필 사진
Popomon
Frontend Developer
(2020/12 ~)

    카테고리

    포스트

    [많이쓰는 UI] 파일 업로드

    2021. 7. 12. 15:42

    꿈가게: To Do List - iOS

    꿈가게: To Do List - Android

    이번 포스팅에서는 드래그 앤 드롭파일 업로드 버튼 두가지를 동시에 이용할 수 있는 파일 업로드 UI를 작성해보려고 합니다. 업로드의 진행 상황을 보여주는 프로그레스바와 업로드 속도는 실제로 파일 서버와 API가 있는 경우에 그 데이터를 불러올 수 있기 때문에, 여기에서는 API를 제외한 화면만을 구성하는데 초점을 맞추었습니다.

    업로드 버튼

    파일 업로드는 <input type="file" /> 태그로 사용해야 하는데, 이 태그는 우리가 원하는 스타일이 적용되지 않습니다. 따라서 다들 속임수를 써서 디자인을 하고 있습니다.

    속임수

    우선, input 태그와 label 태그를 같이 사용하면 label에 특별한 이벤트가 발생합니다. 예를들어, 타입이 라디오 버튼(type="radio")이나 체크박스(type="checkbox")의 경우에는 label 태그의 단어만 클릭해도 선택이 됩니다.

    이러한 특별한 이벤트가 파일업로드(type="file")인 경우에는 label을 선택 시, 파일 업로드 창이 뜨는 형태로 발생합니다.

    그래서 우리는 기존의 input 태그를 숨기고, label 태그를 디자인 하여서 버튼을 만들 수 있습니다.

    마크업

    파일 input 태그에 꼭 multiple을 붙여주어야 여러개의 파일을 한꺼번에 업로드할 수 있습니다.

    <label class="file-label" for="chooseFile">Choose File</label>
    <input class="file" id="chooseFile" type="file" multiple>

    스타일

    .file-label {
      margin-top: 30px;
      background-color: #5b975b;
      color: #fff;
      text-align: center;
      padding: 10px 0;
      width: 65%;
      border-radius: 6px;
      cursor: pointer;
    }
    .file {
      display: none;
    }

    드래그 앤 드롭

    위에있는 그림과 같이, 파일을 드래그해서 업로드를 할 수 있도록 도와주는 상자를 만드는 것은 단순히 div 만을 활용해도 문제가 없습니다. 드래그한 파일이 저 상자 안에서 놓였을 때, 무언가 동작을 할 수 있는 drop 이라는 이벤트를 웹에서 제공해 주고 있기 때문입니다!

    마크업

    <div class="upload-box">
      <div id="drop-file" class="drag-file">
        <img src="https://img.icons8.com/pastel-glyph/2x/image-file.png" alt="파일 아이콘" class="image">
        <p class="message">Drag files to upload</p>
      </div>
    </div>

    스타일

    .upload-box {
      width: calc(50% - 15px);
      box-sizing: border-box;
      margin-right: 30px;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
    }
    .upload-box .drag-file {
      width: 100%;
      height: 360px;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      border: 3px dashed #dbdbdb;
    }
    .upload-box .drag-file.highlight {
      border: 3px dashed red;
    }
    .upload-box .drag-file .image {
      width: 40px;
    }
    .upload-box .drag-file .message {
      margin-bottom: 0;
    }

    자바스크립트

    브라우저에서 드랍 관련한 기본 이벤트가 존재하기 때문에, 기존의 이벤트를 다 막아준 다음에 시작해야 정상적으로 동작합니다. 막은 다음에는 업로드 된 파일을 출력하는 형태까지만 만들어 보겠습니다.

    기존 이벤트 막기

    preventDefault 함수는 이벤트의 추가 전파를 막습니다. 예를들어 자신을 둘러싸고있는 태그에 이벤트가 적용되어 있다고 한다면, 그 이벤트를 나까지는 오게 하지 말아주세요! 라는 의미 입니다.

    stopPropagation 함수는 브라우저가 해당 이벤트에 대해 수행하는 기본적인 작업을 막습니다. 예를들어, 파일을 내려놓는 경우에 파일의 내용을 새 탭에서 보여주는 크롬 브라우저의 동작을 막을 때는 이 함수가 적절합니다.

    function preventDefaults(e) {
      e.preventDefault();
      e.stopPropagation();
    }
     

    What's the difference between event.stopPropagation and event.preventDefault?

    They seem to be doing the same thing... Is one modern and one old? Or are they supported by different browsers? When I handle events myself (without framework) I just always check for both and exe...

    stackoverflow.com

    하이라이팅

    파일을 드래그 해서 상자 안으로 가져갔을 때 표시를 해주기 위한 이벤트 입니다. 파일이 상자 안으로 들어가면 highlight 함수를, 파일이 상자 밖으로 벗어나면 unhighlight 함수를 사용하여 스타일은 변경해 줍니다.

    const dropArea = document.getElementById("drop-file");
    
    function highlight(e) {
      preventDefaults(e);
      dropArea.classList.add("highlight");
    }
    
    function unhighlight(e) {
      preventDefaults(e);
      dropArea.classList.remove("highlight");
    }
    
    dropArea.addEventListener("dragenter", highlight, false);
    dropArea.addEventListener("dragover", highlight, false);
    dropArea.addEventListener("dragleave", unhighlight, false);

    파일업로드

    상자안에 파일을 드래그해서 내려놓았을 때 drop 이벤트가 발생하는데, 그 이벤트 객체에서 files 라는 파일 배열을 가져올 수 있습니다. 이 배열은 선택한 여러개의 파일 데이터를 가지고 있습니다.

    function handleDrop(e) {
      unhighlight(e);
      let dt = e.dataTransfer;
      let files = dt.files;
    
      console.log(files);
      
      // addToFileList
      // ...
    }

    업로드 파일 목록

    위에서 언급한 handleDrop 함수에서 console.log(files); 로 출력한 파일들이 이 목록에 들어가게 됩니다. 

    마크업

    <div id="files" class="files">
      <div class="file">
        <div class="thumbnail">
          <img src="https://img.icons8.com/pastel-glyph/2x/image-file.png" alt="파일타입 이미지" class="image">
        </div>
        <div class="details">
          <header class="header">
            <span class="name">Photo.png</span>
            <span class="size">7.5 mb</span>
          </header>
          <div class="progress">
            <div class="bar"></div>
          </div>
          <div class="status">
            <span class="percent">37% done</span>
            <span class="speed">90KB/sec</span>
          </div>
        </div>
      </div>
    </div>

    스타일

    .files {
      width: calc(50% - 15px);
      box-sizing: border-box;
      overflow: auto;
      height: 360px;
    }
    .files .file {
      display: flex;
      padding: 20px 20px;
      border-bottom: 1px solid #dbdbdb;
    }
    .files .file:last-child {
      margin-bottom: 0px;
      border-bottom: none;
    }
    .files .file .thumbnail {
      display: flex;
      flex: none;
      width: 50px;
      margin-right: 20px;
      align-items: center;
    }
    .files .file .thumbnail .image {
      width: 100%;
    }
    .files .file .details {
      flex: 1;
      display: flex;
      flex-direction: column;
      justify-content: space-between;
    }
    .files .file .details .header {
      display: flex;
    }
    .files .file .details .header .name {
      width: 100px;
      white-space: nowrap;
      text-overflow: ellipsis;
      overflow: hidden;
    }
    .files .file .details .header .size {
      margin-left: auto;
    }
    .files .file .progress {
      position: relative;
      height: 6px;
      background-color: #dbdbdb;
      overflow: hidden;
      margin-top: 4px;
      border-radius: 10px;
    }
    .files .file .progress .bar {
      position: absolute;
      left: 0;
      top: 0;
      height: 100%;
      width: 100%;
      background-color: #5b975b;
    }
    .files .file .status {
      display: flex;
      width: 100%;
    }
    .files .file .status .speed {
      margin-left: auto;
    }

    자바스크립트

    파일이 추가 될 때마다 파일의 데이터를 가지고 DOM을 만들어 주는 함수(renderFile)를 가지고 <div id="file"></div> 태그에 하나씩 추가해 주어야 합니다.

    DOM을 만들어 주는 함수

    function renderFile(file) {
      let fileDOM = document.createElement("div");
      fileDOM.className = "file";
      fileDOM.innerHTML = `
        <div class="thumbnail">
          <img src="https://img.icons8.com/pastel-glyph/2x/image-file.png" alt="파일타입 이미지" class="image">
        </div>
        <div class="details">
          <header class="header">
            <span class="name">${file.name}</span>
            <span class="size">${file.size}</span>
          </header>
          <div class="progress">
            <div class="bar"></div>
          </div>
          <div class="status">
            <span class="percent">100% done</span>
            <span class="speed">90KB/sec</span>
          </div>
        </div>
      `;
      return fileDOM;
    }

    파일 업로드 함수에 렌더링 내용 추가

    const fileList = document.getElementById("files");
    
    function handleDrop(e) {
      unhighlight(e);
      let dt = e.dataTransfer;
      let files = dt.files;
      
      // 추가부분
      files = [...files];
      files.forEach(file => fileList.appendChild(renderFile(file)));
    }

    샘플 코드

    See the Pen Pure javascript fileuplod ui by 홍지성 (@lnbvocxe) on CodePen.

     

    How To Make A Drag-and-Drop File Uploader With Vanilla JavaScript — Smashing Magazine

    In this article, we'll be using "vanilla" ES2015+ JavaScript (no frameworks or libraries) to complete this project, and it is assumed you have a working knowledge of JavaScript in the browser. This example should be compatible with every evergreen browser

    www.smashingmagazine.com

     

    'Publishing > 많이쓰는 UI' 카테고리의 다른 글

    [많이쓰는 UI] 이미지 업로드  (0) 2021.07.12
    [많이쓰는 UI] 댓글  (0) 2021.07.12
    [많이쓰는 UI] 탭 뷰  (0) 2021.07.12
    [많이쓰는 UI] 로딩 화면  (0) 2021.07.12
    [많이쓰는 UI] 모달 팝업  (0) 2021.07.12