학습 목표
- FileReader API를 활용한 이미지 미리보기 구현
- hidden input과 div 연결을 통한 사용자 친화적 UI 설계
- Base64 인코딩을 이용한 클라이언트 측 이미지 처리
- 실제 프로젝트에서 적용한 썸네일 게시판 구현 방법
<사진 미리보기의 핵심 개념>
FileReader API란
FileReader API는 웹 브라우저에서 파일을 읽기 위한 JavaScript API이다. 파일을 읽어서 Base64 형식의 데이터 URL로 변환하여 클라이언트 측에서 이미지를 미리보기할 수 있게 해준다.
미리보기 구현의 핵심 요소
- hidden input: 실제 파일 선택을 위한 숨겨진 input 요소
- div 연결: 사용자가 클릭할 수 있는 시각적 영역
- FileReader: 파일을 읽어서 Base64로 변환
- 동적 이미지 표시: 선택된 이미지를 실시간으로 미리보기
<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 인코딩 과정
- 파일 선택: 사용자가 파일을 선택하면
changeInput.files[0]에 파일 객체 저장 - FileReader 생성:
new FileReader()객체 생성 - 파일 읽기:
reader.readAsDataURL()로 파일을 Base64 문자열로 변환 - 이벤트 처리:
reader.onload이벤트에서 변환 완료 시 처리 - 이미지 표시:
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 클릭 → 파일 선택 다이얼로그 열림 → 파일 선택 →
Base64로 변환 → 미리보기 이미지 표시 → 플레이스홀더 숨김
<서버 전송 처리>
폼 데이터 전송
<form action="${pageContext.request.contextPath}/insert.th"
method="post" enctype="multipart/form-data">
<!-- 파일 input들이 자동으로 multipart 데이터로 전송됨 -->
</form>
서버 측 파일 처리
// ThumbnailInsertController.java에서 처리
// Commons FileUpload 라이브러리를 사용하여 파일 업로드 처리
정리 및 요약
사진 미리보기 구현의 핵심:
- hidden input 활용: 사용자 친화적 UI와 실제 파일 처리를 분리
- FileReader API: 클라이언트 측에서 파일을 Base64로 변환
- 동적 DOM 조작: 선택된 이미지를 실시간으로 미리보기 영역에 표시
- 이벤트 연결: div 클릭 → 파일 선택 → 미리보기 표시의 완전한 흐름
사용자 경험 향상 요소:
- 직관적인 UI: 클릭 가능한 영역을 명확히 표시
- 즉시 피드백: 파일 선택 즉시 미리보기 표시
- 시각적 효과: hover 효과와 transition으로 부드러운 인터랙션
- 반응형 디자인: 다양한 화면 크기에 대응
기술적 장점:
- 서버 부하 감소: 클라이언트에서 미리보기 처리
- 빠른 응답: 파일 선택 즉시 미리보기 표시
- 유연한 확장: 여러 이미지에 동일한 패턴 적용 가능
'Servlet' 카테고리의 다른 글
| JSP/Servlet_11) MyBatis프레임워크를 활용한 JDBC DAO 패턴 비교 분석 (0) | 2025.10.17 |
|---|---|
| JSP/Servlet_09) Ajax를 사용한 비동기 방식 (0) | 2025.10.16 |
| JSP/Servlet_08) Pagination 게시글 페이징 처리 (0) | 2025.10.15 |
| JSP/Servlet_07) commons-fileupload2 라이브러리를 활용한 DB 파일 업로드 구현 (0) | 2025.10.14 |
| Servlet_06) 웹 애플리케이션 세션 관리와 보안 강화 (0) | 2025.10.09 |