IT 정보

1분만에 만드는 드래그앤드롭 및 순서 조정 가능한 이미지 파일 업로드 구현 방법

jerry99 2024. 10. 24. 14:17

소개

웹 애플리케이션에서 파일 업로드는 필수적인 기능 중 하나입니다. 특히 이미지 갤러리나 첨부 파일 목록을 구성할 때 사용자가 편리하게 여러 파일을 업로드하고, 그 순서를 쉽게 조정할 수 있는 기능을 제공하면 UX(사용자 경험)를 크게 향상시킬 수 있습니다. 이번 글에서는 JavaScript와 AJAX를 이용해 드래그앤드롭 및 순서 조정 기능을 가진 이미지 파일 업로드를 구현하는 방법을 소개하겠습니다. 전체 코드 예시는 맨 아래에서 참고해주세요 !

 

 

1. 준비물

  • HTML5를 지원하는 최신 브라우저
  • JavaScript (특히 jQuery는 필요하지 않습니다)
  • 기본적인 CSS 지식
  • 파일을 서버에 업로드할 PHP 서버 환경 (선택 사항)

 

2. 구현할 기능 설명

  • 사용자는 여러 이미지를 선택하여 업로드할 수 있습니다.
  • 파일을 미리보기 상태로 볼 수 있으며, 이미지가 아닌 경우 파일 이름만 표시됩니다.
  • 업로드한 파일의 순서를 위아래로 이동하거나, 맨 위로/맨 아래로 이동할 수 있습니다.
  • 이미 업로드한 파일은 삭제할 수 있습니다.

 

3. HTML 코드: 파일 업로드 폼

아래는 파일 입력과 미리보기 리스트를 위한 HTML 구조입니다. 최대 30개의 이미지를 업로드할 수 있도록 설정했습니다.

<div class="multiple-upload-area">
    <div class="input-wrap">
        <p>이미지 일괄등록</p>
        <input type="file" name="bf_file[]" id="fileInput" multiple="multiple" class="frm_file frm_input" title="갤러리 사진첨부" onchange="handleBulkUpload()">
    </div>
    <ul id="fileList" class="file-list"></ul>
    <span class="upload-note">*최대 30장까지 등록가능합니다.</span>
</div>

 

4. JavaScript 코드: 파일 목록 관리 및 순서 변경

아래 JavaScript 코드를 사용하여 파일 목록을 미리보기에 추가하고, 각 파일의 순서를 조정하거나 삭제할 수 있습니다.

<script>
function handleBulkUpload() {
    const fileInput = document.getElementById('fileInput');
    const fileList = document.getElementById('fileList');
    const files = Array.from(fileInput.files);

    fileList.innerHTML = ''; // 기존 파일 목록 초기화

    files.forEach((file, index) => {
        const listItem = document.createElement('li');
        listItem.dataset.index = index;

        // 파일이 이미지인지 확인
        if (file.type.startsWith('image/')) {
            const reader = new FileReader();
            reader.onload = function (e) {
                listItem.innerHTML = `
                <div class="file-item">
                    <img src="${e.target.result}" alt="${file.name}" class="preview-image">
                    <div class="file-controls">
                        <button type="button" onclick="moveFile(${index}, -1)">▲</button>
                        <button type="button" onclick="moveFile(${index}, 1)">▼</button>
                        <button type="button" onclick="moveFileToStart(${index})">맨 위로</button>
                        <button type="button" onclick="moveFileToEnd(${index})">맨 아래로</button>
                        <button type="button" onclick="removeFile(${index})">삭제</button>
                    </div>
                </div>
            `;
            };
            reader.readAsDataURL(file);
        } else {
            listItem.innerHTML = `
                <div class="file-item">
                    <span>${file.name}</span>
                    <div class="file-controls">
                        <button type="button" onclick="moveFile(${index}, -1)">▲</button>
                        <button type="button" onclick="moveFile(${index}, 1)">▼</button>
                        <button type="button" onclick="moveFileToStart(${index})">맨 위로</button>
                        <button type="button" onclick="moveFileToEnd(${index})">맨 아래로</button>
                        <button type="button" onclick="removeFile(${index})">삭제</button>
                    </div>
                </div>
            `;
        }

        fileList.appendChild(listItem);
    });
}

function moveFile(index, direction) {
    const fileInput = document.getElementById('fileInput');
    const files = Array.from(fileInput.files);
    const newIndex = index + direction;
    if (newIndex < 0 || newIndex >= files.length) return;

    const movedFile = files.splice(index, 1)[0];
    files.splice(newIndex, 0, movedFile);

    updateFileList(files);
}

function moveFileToStart(index) {
    const fileInput = document.getElementById('fileInput');
    const files = Array.from(fileInput.files);

    const movedFile = files.splice(index, 1)[0];
    files.unshift(movedFile);

    updateFileList(files);
}

function moveFileToEnd(index) {
    const fileInput = document.getElementById('fileInput');
    const files = Array.from(fileInput.files);

    const movedFile = files.splice(index, 1)[0];
    files.push(movedFile);

    updateFileList(files);
}

function removeFile(index) {
    const fileInput = document.getElementById('fileInput');
    const files = Array.from(fileInput.files);

    files.splice(index, 1);
    updateFileList(files);
}

function updateFileList(files) {
    const dataTransfer = new DataTransfer();
    files.forEach(file => dataTransfer.items.add(file));
    document.getElementById('fileInput').files = dataTransfer.files;

    handleBulkUpload(); // 파일 목록 다시 렌더링
}

</script>

 

5. CSS 코드: 사용자 친화적인 스타일 적용

파일 목록과 버튼을 보다 모던하게 보이게 하기 위한 CSS입니다.

<style>
.file-item {
    display: flex;
    align-items: center;
    background-color: #f8f9fa;
    border: 1px solid #dee2e6;
    border-radius: 8px;
    padding: 10px;
    margin-bottom: 10px;
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
    transition: background-color 0.3s ease;
}

.file-item:hover {
    background-color: #e9ecef;
}

.file-item img.preview-image {
    width: 60px;
    height: 60px;
    margin-right: 15px;
    border-radius: 4px;
    object-fit: cover;
    border: 1px solid #ccc;
}

.file-controls {
    margin-left: auto;
    display: flex;
    gap: 10px;
}

.file-controls button {
    background-color: #007bff;
    color: #ffffff;
    border: none;
    padding: 5px 10px;
    cursor: pointer;
    border-radius: 4px;
    font-size: 14px;
    transition: background-color 0.3s ease, transform 0.2s ease;
}

.file-controls button:hover {
    background-color: #0056b3;
    transform: translateY(-2px);
}

</style>

6. 결과 미리보기

실 코드를 반영하여 웹에 적용했을때)

 

7. 결론

  • 파일 업로드 기능에 순서 변경 및 삭제 기능을 추가하면, 사용자가 더욱 직관적으로 이미지를 관리할 수 있습니다.
  • JavaScript와 CSS를 활용하여 기능을 구현하고, 더 나아가 서버 측에서 파일을 처리하는 방법까지 확장할 수 있습니다.
  • 이번 글에서 소개한 방법을 통해 더 나은 사용자 경험을 제공하는 웹 애플리케이션을 개발해보세요!

 

전체 코드 예시 )

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Image Upload with Sorting</title>
    <style>
        .multiple-upload-area {
            background: #ffffff;
            border: 1px solid #dee2e6;
            border-radius: 8px;
            padding: 20px;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
            max-width: 600px;
            margin: 0 auto;
        }

        .multiple-upload-area .input-wrap {
            display: flex;
            flex-direction: column;
            gap: 10px;
            margin-bottom: 20px;
        }

        .multiple-upload-area .input-wrap p {
            font-size: 16px;
            font-weight: bold;
            color: #343a40;
        }

        .multiple-upload-area input[type="file"] {
            padding: 5px;
            border: 1px solid #ced4da;
            border-radius: 4px;
            cursor: pointer;
        }

        .file-list {
            list-style: none;
            padding: 0;
            margin: 0;
        }

        .file-item {
            display: flex;
            align-items: center;
            background-color: #f8f9fa;
            border: 1px solid #dee2e6;
            border-radius: 8px;
            padding: 10px;
            margin-bottom: 10px;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
            transition: background-color 0.3s ease;
        }

        .file-item:hover {
            background-color: #e9ecef;
        }

        .file-item img.preview-image {
            width: 60px;
            height: 60px;
            margin-right: 15px;
            border-radius: 4px;
            object-fit: cover;
            border: 1px solid #ccc;
        }

        .file-controls {
            margin-left: auto;
            display: flex;
            gap: 10px;
        }

        .file-controls button {
            background-color: #007bff;
            color: #ffffff;
            border: none;
            padding: 5px 10px;
            cursor: pointer;
            border-radius: 4px;
            font-size: 14px;
            transition: background-color 0.3s ease, transform 0.2s ease;
        }

        .file-controls button:hover {
            background-color: #0056b3;
            transform: translateY(-2px);
        }

        .file-controls button:active {
            background-color: #003d80;
            transform: translateY(0);
        }

        .upload-note {
            font-size: 12px;
            color: #6c757d;
        }
    </style>
</head>
<body>
    <div class="multiple-upload-area">
        <div class="input-wrap">
            <p>이미지 일괄등록</p>
            <input type="file" name="bf_file[]" id="fileInput" multiple="multiple" class="frm_file frm_input" title="갤러리 사진첨부" onchange="handleBulkUpload()">
        </div>
        <ul id="fileList" class="file-list"></ul>
        <span class="upload-note">*최대 30장까지 등록가능합니다.</span>
    </div>

    <script>
        function handleBulkUpload() {
            const fileInput = document.getElementById('fileInput');
            const fileList = document.getElementById('fileList');
            const files = Array.from(fileInput.files);

            fileList.innerHTML = ''; // 기존 파일 목록 초기화

            files.forEach((file, index) => {
                const listItem = document.createElement('li');
                listItem.dataset.index = index;

                // 파일이 이미지인지 확인
                if (file.type.startsWith('image/')) {
                    const reader = new FileReader();
                    reader.onload = function (e) {
                        listItem.innerHTML = `
                            <div class="file-item">
                                <img src="${e.target.result}" alt="${file.name}" class="preview-image">
                                <div class="file-controls">
                                    <button type="button" onclick="moveFile(${index}, -1)">▲</button>
                                    <button type="button" onclick="moveFile(${index}, 1)">▼</button>
                                    <button type="button" onclick="moveFileToStart(${index})">맨 위로</button>
                                    <button type="button" onclick="moveFileToEnd(${index})">맨 아래로</button>
                                    <button type="button" onclick="removeFile(${index})">삭제</button>
                                </div>
                            </div>
                        `;
                    };
                    reader.readAsDataURL(file);
                } else {
                    listItem.innerHTML = `
                        <div class="file-item">
                            <span>${file.name}</span>
                            <div class="file-controls">
                                <button type="button" onclick="moveFile(${index}, -1)">▲</button>
                                <button type="button" onclick="moveFile(${index}, 1)">▼</button>
                                <button type="button" onclick="moveFileToStart(${index})">맨 위로</button>
                                <button type="button" onclick="moveFileToEnd(${index})">맨 아래로</button>
                                <button type="button" onclick="removeFile(${index})">삭제</button>
                            </div>
                        </div>
                    `;
                }

                fileList.appendChild(listItem);
            });
        }

        function moveFile(index, direction) {
            const fileInput = document.getElementById('fileInput');
            const files = Array.from(fileInput.files);

            const newIndex = index + direction;
            if (newIndex < 0 || newIndex >= files.length) return;

            const movedFile = files.splice(index, 1)[0];
            files.splice(newIndex, 0, movedFile);

            updateFileList(files);
        }

        function moveFileToStart(index) {
            const fileInput = document.getElementById('fileInput');
            const files = Array.from(fileInput.files);

            const movedFile = files.splice(index, 1)[0];
            files.unshift(movedFile);

            updateFileList(files);
        }

        function moveFileToEnd(index) {
            const fileInput = document.getElementById('fileInput');
            const files = Array.from(fileInput.files);

            const movedFile = files.splice(index, 1)[0];
            files.push(movedFile);

            updateFileList(files);
        }

        function removeFile(index) {
            const fileInput = document.getElementById('fileInput');
            const files = Array.from(fileInput.files);

            files.splice(index, 1);
            updateFileList(files);
        }

        function updateFileList(files) {
            const dataTransfer = new DataTransfer();
            files.forEach(file => dataTransfer.items.add(file));
            document.getElementById('fileInput').files = dataTransfer.files;

            handleBulkUpload(); // 파일 목록 다시 렌더링
        }
    </script>
</body>
</html>

 

 

이 글은 실제 코드 예시와 함께 따라 하면서 구현할 수 있게 되어 있어, 독자들이 쉽게 자신만의 프로젝트에 적용할 수 있을 것입니다.