컴포넌트/필드 컴포넌트

ImageFile (이미지파일)

VanillaFront 2026. 9. 14. 17:56

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 흐름:

  1. 초기 프로필 이미지 로드
  2. 사용자가 이미지 우클릭 → 편집 버튼 나타남
  3. 파일 선택 → 즉시 미리보기 + 서버 업로드
  4. 실패 시 자동 리셋

13. 알아두면 좋을 주의사항

  1. tagName 내부는 'file' — 소스에서 this.tagName = 'file'로 세팅 (va_component.js:9957). 등록명은 imageFile이지만 내부 tagName은 file이라 특정 조회 로직에서 헷갈릴 수 있음.
  2. 미리보기는 로컬만 — 파일 선택 즉시 FileReader로 DataURL 생성. 서버 업로드는 별도 처리 필요.
  3. multiSelect: true여도 미리보기는 첫 파일만 — 여러 이미지 미리보기가 필요하면 커스텀.
  4. accept 옵션 미노출 — 이미지만 허용하려면 DOM 직접 세팅.
  5. 우클릭 편집/취소 UX 인지 필요 — 사용자에게 명시적 안내 필요 (숨겨진 인터랙션).
  6. visibleField: false 상태의 파일 재선택 — 우클릭으로만 접근. 모바일에선 우클릭이 어색하니 데스크톱 위주.
  7. 드래그앤드롭 API 브라우저 호환성 — File System Access API 미지원 브라우저에선 fallback으로 처리하지만 세부 동작 차이 있음.
  8. change 이벤트가 두 곳에서 dispatch — 파일 선택과 드롭 모두에서. 콜백에 중복 실행 조심.
  9. getFiles() 반환은 FileList — 배열 아님.
  10. setSrc()로 서버 이미지 갱신 — 업로드 후 서버 URL로 미리보기 유지.
  11. 크기 검증은 개발자 몫 — HTML 표준 대신 JS 로직으로.
  12. 이미지 로딩 실패 시 대체 아이콘  noImgSrc: 'ico_image_fill' 자동 (내부 Image 컴포넌트).

14. imageFile vs file vs fileDropUpload 선택

상황추천

프로필·대표 이미지 (미리보기) imageFile
일반 파일 첨부 file / fileField
여러 파일 대량 업로드 fileDropUpload
드래그앤드롭이 주 UI fileDropUpload
이미지 크롭/편집 커스텀 (별도 라이브러리)
간단 첨부 버튼 fileButton

참고