Va.ImageFile — 이미지 파일 업로드 + 미리보기
일반 Va.File에 이미지 미리보기·드래그앤드롭·우클릭 편집 기능이 통합된 컴포넌트. 프로필 사진, 게시글 대표 이미지, 상품 사진 같은 이미지 특화 업로드 UI를 한 번에 제공합니다.
- 클래스: Va.ImageFile — va_component.js:9954
- short name: imageFile
- 상속: Va.PureField (File과 형제)
- isContainer: true
- 베이스 CSS: va-image-file

1. 기본 사용
{
tagName: 'imageFile',
src: '/uploads/profile.jpg', // 초기 이미지
shape: 'circular',
onChange: 'onImageChange'
}
렌더 결과:
- 상단 이미지 영역: 미리보기 (초기 src 또는 선택된 파일 이미지)
- 하단: 파일명 표시 + File 버튼 + Reset 버튼 (visibleField=false면 숨김)
- 드래그앤드롭 지원: 이미지 위에 파일을 드롭
- 우클릭: 편집/취소 버튼 표시
2. File / ImageFile의 차이
항목Va.FileVa.ImageFile
| 미리보기 | ✕ | ✓ (선택 즉시 이미지 표시) |
| 드래그앤드롭 | ✕ | ✓ (파일 드롭 지원) |
| 우클릭 편집 | ✕ | ✓ (편집/취소 버튼) |
| src 옵션 | ✕ | ✓ (초기 이미지 URL) |
| shape | ✕ | ✓ (원형/사각 등) |
| bordered/shadow | ✕ | ✓ |
| 내부 Va.Image 컴포넌트 | ✕ | ✓ |
| 하단 필드 숨김 | ✕ | ✓ (visibleField: false) |
한 줄 요약: "이미지 특화 파일 업로드 + 드래그앤드롭 + 미리보기 통합 컴포넌트."
3. 주요 속성
이미지 관련
속성기본값설명
| src | — | 초기 이미지 URL |
| shape | — | 이미지 형태 (circular, square, rounded 등 — Va.Image 옵션 따름) |
| bordered | — | 테두리 표시 |
| shadow | — | 그림자 |
| noImgSrc | (내부: 'ico_image_fill') | 이미지 없을 때 표시할 대체 아이콘 |
| naturalSize | — | 원본 크기로 표시 |
파일 업로드 관련 (File과 공통)
속성기본값설명
| fileButtonText | 'File' | File 버튼 텍스트 |
| resetButtonText | 'reset' | Reset 버튼 텍스트 |
| displayText | 'Choose file' | 초기 표시 텍스트 |
| multiSelect | false | 다중 파일 (미리보기는 첫 파일만) |
| visibleField | true | 하단 파일 필드 영역 표시 여부. false면 이미지만 보임 (아이콘 스타일 UI) |
⚠️ multiSelect: true여도 미리보기는 첫 파일만 — event.target.files[0]만 FileReader로 읽음.
⚠️ accept / capture 등 표준 파일 속성 미노출 — File과 동일하게 필요 시 DOM 직접 세팅.
4. 사용자 상호작용 3가지
1) File 버튼으로 선택 (기본)
일반 파일 다이얼로그로 선택 → 즉시 미리보기 반영.
2) 드래그앤드롭
이미지 영역에 파일을 드래그 → 미리보기 반영 + change + filedrop 이벤트 dispatch.
내부적으로 File System Access API / webkitGetAsEntry / getAsFile 순으로 지원 확인해 처리합니다 (va_component.js:10044-10058).
3) 우클릭으로 편집/취소 진입
이미지나 dropDiv에 우클릭 → 편집 버튼 + 취소 버튼이 표시됨.
- 편집 클릭 → 이미지 숨기고 dropDiv + 하단 필드 노출 (재선택 모드)
- 취소 클릭 → 이미지 복원
아이콘 스타일 UI(visibleField: false) 상황에서 유용. 평상시엔 이미지만 보이고, 우클릭으로 변경 UI 진입.
5. 이벤트
이벤트시그니처발생 시점
| change | (component, element, files, evt) | 파일 선택 완료 또는 드래그 드롭 완료 |
| filedrop | (component, element, result, evt) | 드래그 드롭 시 (result는 DataURL) |
| reset | (component, fieldElement, evt) | Reset 버튼 클릭 |
| click | (component, element, evt) | File 버튼 클릭 |
| contextmenu | — | 이미지/dropDiv 우클릭 (편집 버튼 자동 표시) |
change 콜백 예시
onImageChange(comp, el, files, evt) {
const file = files[0];
console.log('이미지:', file.name, file.type, file.size);
// 이미 comp 내부에서 미리보기는 자동 반영됨
// 서버 업로드가 필요하면:
const formData = new FormData();
formData.append('image', file);
// ...
}
6. 메서드
이미지·파일
메서드설명
| setSrc(src) | 미리보기 이미지 URL 설정 (내부 Image 컴포넌트에 위임) |
| getFiles() | FileList 반환 |
| onDropFile(file) | 드롭된 파일 처리 (내부 사용용, 직접 호출 잘 안 함) |
상태 (PureField 상속)
메서드설명
| setDisabled(bool) / setReadOnly(bool) | 상태 (두 버튼 자동 전파) |
7. 내부 컴포넌트 (innerComponents)
Va.ImageFile은 네 개의 내부 컴포넌트를 소유:
내부 컴포넌트접근 방법역할
| imageComponent | component.imageComponent | 미리보기 이미지 (Va.Image) |
| fileButtonComponent | component.fileButtonComponent | 파일 선택 버튼 |
| resetButtonComponent | component.resetButtonComponent | 리셋 버튼 |
| editButtonComponent | component.editButtonComponent | 우클릭 시 표시되는 편집 버튼 |
| cancelButtonComponent | component.cancelButtonComponent | 우클릭 시 표시되는 취소 버튼 |
각 컴포넌트를 직접 조작하면 세부 커스터마이즈 가능.
8. 내부 구조
<div elname="element" class="va-image-file" ...>
<div elname="imageDiv" class="image-div"> ← 이미지 영역
<div cpname="image" class="va-image" ...> ← 미리보기 이미지
<div elname="dropDiv" class="drop-div" ← 드래그앤드롭 영역
style="display:none"></div>
<div cpname="editButton" style="display:none">편집</div>
<div cpname="cancelButton" style="display:none">취소</div>
</div>
<div elname="fieldWrapper" class="field-wrapper"
style="display:none|flex"> ← visibleField에 따라
<input elname="field" type="file" style="display:none">
<div elname="display" class="display">Choice file</div>
<div cpname="fileButton">File</div>
<div cpname="resetButton">reset</div>
</div>
</div>
핵심:
- 이미지 영역이 최상단, 하단에 파일 필드 (또는 숨김)
- <input type="file">은 항상 숨김. 실제 조작은 파일 버튼과 드래그
- FileReader로 로컬 이미지를 즉시 DataURL로 변환해 미리보기
9. visibleField: false — 아이콘 스타일 UI
파일 선택 UI를 완전히 숨기고 이미지만 보여주는 모드:
{
tagName: 'imageFile',
src: '/uploads/avatar.jpg',
visibleField: false,
shape: 'circular',
style: { width: '100px', height: '100px' }
}
동작:
- 하단 파일 필드 완전히 숨김
- 이미지에 우클릭 → 편집/취소 버튼 표시 → 편집 클릭 시 파일 선택 UI 등장
- 프로필 사진 아이콘처럼 자연스러운 UI
10. 언제 쓰나
ImageFile이 맞을 때
- 프로필 사진 업로드
- 게시글 대표 이미지 첨부
- 상품 사진 등록
- 명함·증빙 이미지 (드래그앤드롭이 편리한 상황)
- 미리보기가 필요한 이미지 첨부 상황
다른 걸 쓸 때
- 이미지가 아닌 일반 파일 → Va.File / Va.FileField
- 여러 이미지 대량 업로드 → Va.FileDropUpload
- 이미지 편집 (크롭·필터) → 별도 커스텀 필요
- 라벨 붙은 폼 필드 → Va.ImageFileField (있다면)
11. 흔한 조합 예시
// 표준 (파일 필드 함께 표시)
{
tagName: 'imageFile',
src: '/uploads/current.jpg',
onChange: 'onImageChange'
}
// 프로필 사진 (원형, 필드 숨김)
{
tagName: 'imageFile',
src: '/uploads/avatar.jpg',
shape: 'circular',
visibleField: false,
style: { width: '120px', height: '120px' }
}
// 상품 이미지 (테두리·그림자)
{
tagName: 'imageFile',
src: '/products/main.jpg',
bordered: true,
shadow: true,
fileButtonText: '이미지 변경',
resetButtonText: '삭제'
}
// 원본 크기 표시
{
tagName: 'imageFile',
src: '/uploads/large.jpg',
naturalSize: true
}
// 이미지만 허용 (accept 직접 세팅)
mounted() {
this.getRef('img').fieldElement.setAttribute('accept', 'image/*');
}
12. 실전 예 — 프로필 사진 업로드
class ProfileEdit extends Va.View {
async mounted() {
this.getRef('avatar').fieldElement.setAttribute('accept', 'image/*');
// 현재 프로필 로드
const res = await ProfileService.get(this);
if (res.result && res.data.avatarUrl) {
this.getRef('avatar').setSrc(res.data.avatarUrl);
}
}
onAvatarChange(comp, el, files, evt) {
const file = files[0];
if (!file) return;
// 크기·타입 검증
if (file.size > 2 * 1024 * 1024) {
new Va.Alert({ title: '용량 초과', message: '2MB 이하만 허용됩니다' }).show(this);
comp.resetButtonComponent.element.click();
return;
}
if (!file.type.startsWith('image/')) {
new Va.Alert({ title: '오류', message: '이미지 파일만 첨부 가능' }).show(this);
comp.resetButtonComponent.element.click();
return;
}
// 즉시 서버 업로드 (프로필은 즉시 반영이 자연스러움)
const formData = new FormData();
formData.append('avatar', file);
this.showMasking();
ProfileService.uploadAvatar(this, formData, (view, ok, res) => {
view.hideMasking();
if (ok) {
// 서버 URL로 미리보기 갱신 (선택사항, 이미 로컬로 반영됨)
comp.setSrc(res.data.avatarUrl);
new Va.Alert({ title: '완료', message: '프로필 사진이 변경되었습니다' }).show(view);
}
});
}
config() {
return {
tagName: 'page',
tags: [{
tagName: 'panel',
tags: [
{ tagName: 'h2', innerHTML: '프로필 편집' },
{
tagName: 'div',
layout: 'ds-flex fd-column ai-center gap-m',
tags: [
{
tagName: 'imageFile',
ref: 'avatar',
shape: 'circular',
bordered: true,
visibleField: false, // 이미지만 노출
style: { width: '150px', height: '150px' },
onChange: 'onAvatarChange'
},
{
tagName: 'div',
innerHTML: '이미지를 우클릭하여 변경할 수 있습니다',
style: { fontSize: '12px', color: 'gray' }
}
]
}
]
}]
};
}
}
UX 흐름:
- 초기 프로필 이미지 로드
- 사용자가 이미지 우클릭 → 편집 버튼 나타남
- 파일 선택 → 즉시 미리보기 + 서버 업로드
- 실패 시 자동 리셋
13. 알아두면 좋을 주의사항
- tagName 내부는 'file' — 소스에서 this.tagName = 'file'로 세팅 (va_component.js:9957). 등록명은 imageFile이지만 내부 tagName은 file이라 특정 조회 로직에서 헷갈릴 수 있음.
- 미리보기는 로컬만 — 파일 선택 즉시 FileReader로 DataURL 생성. 서버 업로드는 별도 처리 필요.
- multiSelect: true여도 미리보기는 첫 파일만 — 여러 이미지 미리보기가 필요하면 커스텀.
- accept 옵션 미노출 — 이미지만 허용하려면 DOM 직접 세팅.
- 우클릭 편집/취소 UX 인지 필요 — 사용자에게 명시적 안내 필요 (숨겨진 인터랙션).
- visibleField: false 상태의 파일 재선택 — 우클릭으로만 접근. 모바일에선 우클릭이 어색하니 데스크톱 위주.
- 드래그앤드롭 API 브라우저 호환성 — File System Access API 미지원 브라우저에선 fallback으로 처리하지만 세부 동작 차이 있음.
- change 이벤트가 두 곳에서 dispatch — 파일 선택과 드롭 모두에서. 콜백에 중복 실행 조심.
- getFiles() 반환은 FileList — 배열 아님.
- setSrc()로 서버 이미지 갱신 — 업로드 후 서버 URL로 미리보기 유지.
- 크기 검증은 개발자 몫 — HTML 표준 대신 JS 로직으로.
- 이미지 로딩 실패 시 대체 아이콘 — noImgSrc: 'ico_image_fill' 자동 (내부 Image 컴포넌트).
14. imageFile vs file vs fileDropUpload 선택
상황추천
| 프로필·대표 이미지 (미리보기) | imageFile |
| 일반 파일 첨부 | file / fileField |
| 여러 파일 대량 업로드 | fileDropUpload |
| 드래그앤드롭이 주 UI | fileDropUpload |
| 이미지 크롭/편집 | 커스텀 (별도 라이브러리) |
| 간단 첨부 버튼 | fileButton |
참고
- API 문서 페이지: https://vanillafront.com/docs.html?theme=light#main#apiimagefile
- 연관: Va.File(형제, 미리보기 없음), Va.Image(내부 미리보기 컴포넌트)
'컴포넌트 > 필드 컴포넌트' 카테고리의 다른 글
| FileDropUploadField (파일드롭 업로드 필드) (0) | 2026.09.14 |
|---|---|
| FileDropUpload (파일드롭 업로드) (0) | 2026.09.14 |
| FileField (파일필드) (1) | 2026.09.14 |
| File (파일) (0) | 2026.09.13 |
| TextareaField (텍스트영역필드) (0) | 2026.09.13 |