JSP/Servlet_10) 썸네일 게시판 사진 미리보기 구현
2025. 10. 17. 09:48

학습 목표

  • FileReader API를 활용한 이미지 미리보기 구현
  • hidden input과 div 연결을 통한 사용자 친화적 UI 설계
  • Base64 인코딩을 이용한 클라이언트 측 이미지 처리
  • 실제 프로젝트에서 적용한 썸네일 게시판 구현 방법

<사진 미리보기의 핵심 개념>

FileReader API란

FileReader API는 웹 브라우저에서 파일을 읽기 위한 JavaScript API이다. 파일을 읽어서 Base64 형식의 데이터 URL로 변환하여 클라이언트 측에서 이미지를 미리보기할 수 있게 해준다.

미리보기 구현의 핵심 요소

  1. hidden input: 실제 파일 선택을 위한 숨겨진 input 요소
  2. div 연결: 사용자가 클릭할 수 있는 시각적 영역
  3. FileReader: 파일을 읽어서 Base64로 변환
  4. 동적 이미지 표시: 선택된 이미지를 실시간으로 미리보기

<HTML 구조 설계>

기본 HTML 구조

<!-- 대표이미지 영역 -->
<div class="thumbnail-preview" onclick="chooseFile('#file1')">
    <img id="tumbnail-img" style="display: none;">
    <div class="upload-placeholder" id="thumbnail-placeholder">
        클릭하여 대표이미지를 선택하세요
    </div>
</div>

<!-- 숨겨진 파일 입력 -->
<div style="display: none;">
    <input type="file" name="file1" id="file1" required 
           onchange="loadImg(this, '#tumbnail-img', '#thumbnail-placeholder')">
</div>

상세이미지 영역 구조

<div class="detail-images">
    <div class="detail-image-preview" onclick="chooseFile('#file2')">
        <img id="content-img1" style="display: none;">
        <div class="upload-placeholder" id="img1-placeholder">이미지 1</div>
    </div>
    <div class="detail-image-preview" onclick="chooseFile('#file3')">
        <img id="content-img2" style="display: none;">
        <div class="upload-placeholder" id="img2-placeholder">이미지 2</div>
    </div>
    <div class="detail-image-preview" onclick="chooseFile('#file4')">
        <img id="content-img3" style="display: none;">
        <div class="upload-placeholder" id="img3-placeholder">이미지 3</div>
    </div>
</div>

<CSS 스타일링>

미리보기 영역 스타일

.thumbnail-preview {
    display: inline-block;
    width: 100%;
    max-width: 400px;
    height: 250px;
    border: 2px dashed #ddd;
    border-radius: 8px;
    background: #f8f9fa;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: all 0.3s ease;
    overflow: hidden;
}

.thumbnail-preview:hover {
    border-color: #4b89fc;
    background: #f0f5ff;
}

.thumbnail-preview img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}

상세이미지 미리보기 스타일

.detail-image-preview {
    width: 180px;
    height: 140px;
    border: 2px dashed #ddd;
    border-radius: 8px;
    background: #f8f9fa;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: all 0.3s ease;
    overflow: hidden;
}

.detail-image-preview:hover {
    border-color: #4b89fc;
    background: #f0f5ff;
}

<JavaScript 구현>

파일 선택 함수

function chooseFile(selector) {
    const fileInput = document.querySelector(selector);
    fileInput.click(); // 숨겨진 input을 클릭한 것처럼 동작
}

이미지 미리보기 핵심 함수

function loadImg(changeInput, targetImgId, placeholderId) {
    const img = document.querySelector(targetImgId);
    const placeholder = document.querySelector(placeholderId);

    if(changeInput.files.length > 0) { // 선택된 파일이 있는 경우
        const reader = new FileReader();  
        // 파일을 읽어서 Base64 형식의 인코딩 된 문자열(데이터 URL)로 변환
        reader.readAsDataURL(changeInput.files[0]);

        // 변환이 완료 되었을 때 load 이벤트 실행 
        reader.onload = function(ev) {
            img.src = ev.target.result;  // ev.target : reader 객체
            img.style.display = 'block';
            placeholder.style.display = 'none';
        }
    } else {
        img.src = null;
        img.style.display = 'none';
        placeholder.style.display = 'block';
    }
}

함수 호출 방식

<!-- 각 파일 input에 onchange 이벤트 연결 -->
<input type="file" name="file1" id="file1" required 
       onchange="loadImg(this, '#tumbnail-img', '#thumbnail-placeholder')">
<input type="file" name="file2" id="file2" 
       onchange="loadImg(this, '#content-img1', '#img1-placeholder')">
<input type="file" name="file3" id="file3" 
       onchange="loadImg(this, '#content-img2', '#img2-placeholder')">
<input type="file" name="file4" id="file4" 
       onchange="loadImg(this, '#content-img3', '#img3-placeholder')">

<FileReader API 동작 원리>

Base64 인코딩 과정

  1. 파일 선택: 사용자가 파일을 선택하면 changeInput.files[0]에 파일 객체 저장
  2. FileReader 생성: new FileReader() 객체 생성
  3. 파일 읽기: reader.readAsDataURL()로 파일을 Base64 문자열로 변환
  4. 이벤트 처리: reader.onload 이벤트에서 변환 완료 시 처리
  5. 이미지 표시: ev.target.result를 img 태그의 src에 할당

데이터 URL 형식

data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD...
  • data:: 데이터 URL 스키마
  • image/jpeg: MIME 타입
  • base64: 인코딩 방식
  • /9j/4AAQ...: Base64로 인코딩된 이미지 데이터

<사용자 경험 개선 요소>

시각적 피드백

.upload-placeholder {
    color: #999;
    font-size: 0.9rem;
}

.thumbnail-preview:hover {
    border-color: #4b89fc;
    background: #f0f5ff;
}

반응형 디자인

.thumbnail-preview img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain; /* 비율 유지하며 영역에 맞춤 */
}

다중 이미지 처리

  • 대표이미지: 1개 (필수)
  • 상세이미지: 최대 3개 (선택)
  • 각각 독립적인 미리보기 영역 제공

<실제 동작 과정>

1단계: 사용자 클릭

// div 클릭 시 숨겨진 input이 클릭된 것처럼 동작
<div class="thumbnail-preview" onclick="chooseFile('#file1')">

2단계: 파일 선택

function chooseFile(selector) {
    const fileInput = document.querySelector(selector);
    fileInput.click(); // 파일 선택 다이얼로그 열림
}

3단계: 파일 변경 감지

// input의 onchange 이벤트가 자동으로 발생
onchange="loadImg(this, '#tumbnail-img', '#thumbnail-placeholder')"

4단계: 이미지 미리보기

reader.onload = function(ev) {
    img.src = ev.target.result;  // Base64 데이터를 img src에 할당
    img.style.display = 'block'; // 이미지 표시
    placeholder.style.display = 'none'; // 플레이스홀더 숨김
}

실행 결과:

div 영역 클릭 시 input file
우리가 아는 그 화면
파일 선택시 올라간다
상세이미지도 잘 올라간다.

[ 실행 순서 ]

사용자가 div 클릭 → 파일 선택 다이얼로그 열림 → 파일 선택 →
Base64로 변환 → 미리보기 이미지 표시 → 플레이스홀더 숨김

<서버 전송 처리>

폼 데이터 전송

<form action="${pageContext.request.contextPath}/insert.th" 
      method="post" enctype="multipart/form-data">
    <!-- 파일 input들이 자동으로 multipart 데이터로 전송됨 -->
</form>

서버 측 파일 처리

// ThumbnailInsertController.java에서 처리
// Commons FileUpload 라이브러리를 사용하여 파일 업로드 처리

정리 및 요약

사진 미리보기 구현의 핵심:

  1. hidden input 활용: 사용자 친화적 UI와 실제 파일 처리를 분리
  2. FileReader API: 클라이언트 측에서 파일을 Base64로 변환
  3. 동적 DOM 조작: 선택된 이미지를 실시간으로 미리보기 영역에 표시
  4. 이벤트 연결: div 클릭 → 파일 선택 → 미리보기 표시의 완전한 흐름

사용자 경험 향상 요소:

  • 직관적인 UI: 클릭 가능한 영역을 명확히 표시
  • 즉시 피드백: 파일 선택 즉시 미리보기 표시
  • 시각적 효과: hover 효과와 transition으로 부드러운 인터랙션
  • 반응형 디자인: 다양한 화면 크기에 대응

기술적 장점:

  • 서버 부하 감소: 클라이언트에서 미리보기 처리
  • 빠른 응답: 파일 선택 즉시 미리보기 표시
  • 유연한 확장: 여러 이미지에 동일한 패턴 적용 가능

전체 코드 : GitHub 아이콘