이번 포스팅에서는 드래그 앤 드롭과 파일 업로드 버튼 두가지를 동시에 이용할 수 있는 파일 업로드 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();
}
파일을 드래그 해서 상자 안으로 가져갔을 때 표시를 해주기 위한 이벤트 입니다. 파일이 상자 안으로 들어가면 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> 태그에 하나씩 추가해 주어야 합니다.
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.
[많이쓰는 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 |