Image Upload — 画像アップロード&プレビュー

フォーム入力 初級

このコンポーネントについて

プロフィール画像の設定・商品画像の登録・投稿フォームの添付など、画像ファイルを受け付けるUIは多くのWebアプリで必要になります。 このページでは、ファイル選択ダイアログまたはドラッグ&ドロップで画像を選び、サーバーに送信する前にプレビュー表示するUIの実装例を紹介します。

このデモはサーバーへの送信を行いません。選択した画像をブラウザ上でプレビュー確認してダウンロードするところまでを体験できる擬似デモです。 実際のアップロード処理(サーバーへの送信)は、サンプルコードにコメントアウトで記載したFetch APIのコードを参考にしてください。

  • ファイル選択 / ドラッグ&ドロップ — クリックでファイル選択ダイアログを開くか、エリアへのドラッグ&ドロップで画像を読み込む
  • プレビュー表示 — 選択した画像をFileReader APIでBase64に変換し、ページ上にプレビュー表示する
  • ファイル情報表示 — ファイル名・ファイルサイズ(KB換算)を表示する
  • ダウンロード — プレビュー後にダウンロードボタンを押すと、選択したファイルをそのまま(加工なし)ダウンロードできる
  • 形式バリデーションaccept="image/*" およびMIMEタイプの確認で画像以外を弾く
  • 再選択対応 — 別の画像を選ぶとプレビューが上書き更新される

実装のポイント・注意点

<input type="file"><label>for 属性でクリックをトリガーできるため、inputを display: none にしてラベル全体をクリックエリアにするのが定番の実装です。

ドラッグ&ドロップを実装する際は dragover イベントで必ず e.preventDefault() を呼ぶ必要があります。 これを忘れると drop イベントが発火せず、ブラウザがファイルを別タブで開いてしまいます。

ダウンロードボタンは URL.createObjectURL(file) で Blob URL を生成し、<a> タグの hrefdownload 属性にセットする方法が最もシンプルです。 FileReader でBase64に変換してからダウンロードする方法もありますが、大きなファイルでは処理が重くなります。

HTML・CSS・バニラJavaScriptのみで実装しており、フレームワーク不要でコピペすぐに動きます。

デモ

※ このデモはサーバーへの送信を行いません。選択した画像のプレビューとダウンロードを体験できる擬似デモです。

サンプルソース

3つのファイルを同じフォルダに保存し、index.html をブラウザで開くとすぐに動作確認できます。
ファイル名:index.html / style.css / script.js — 保存時の文字コードは UTF-8 を指定してください。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>画像アップロード&プレビュー</title>
  <link rel="stylesheet" href="./style.css">
</head>
<body>

<div class="img-upload">
  <label class="img-upload__area" id="upload-area" for="image-input">
    <svg class="img-upload__icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
      fill="none" stroke="currentColor" stroke-width="1.5"
      stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
      <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
      <polyline points="17 8 12 3 7 8"/>
      <line x1="12" y1="3" x2="12" y2="15"/>
    </svg>
    <span class="img-upload__text">クリックまたはドラッグ&ドロップで画像を選択</span>
    <span class="img-upload__sub">PNG / JPG / GIF / WebP</span>
    <input type="file" id="image-input" accept="image/*" class="img-upload__input">
  </label>

  <div class="img-upload__preview" id="preview-area" hidden>
    <img class="img-upload__img" id="preview-img" src="" alt="プレビュー">
    <p class="img-upload__info" id="file-info"></p>
    <a class="img-upload__download" id="download-btn" download>ダウンロード</a>
  </div>

  <p class="img-upload__note">※ このデモはサーバーへの送信を行いません。選択した画像のプレビューとダウンロードを体験できる擬似デモです。</p>
</div>

<script src="./script.js"></script>
</body>
</html>
/* 画像アップロード&プレビュー — style.css */
:root {
  --upload-border:       #C8D5E6;
  --upload-border-hover: #2B7FE8;
  --upload-bg:           #F7F9FC;
  --upload-bg-dragover:  #EBF2FF;
  --upload-icon-color:   #9AA5B4;
  --upload-text-color:   #5A6A7A;
  --upload-sub-color:    #9AA5B4;
  --upload-note-color:   #B0BAC8;
}

*, *::before, *::after { box-sizing: border-box; }

body {
  font-family: sans-serif;
  padding: 32px 16px;
  background: #f8f9fa;
  display: flex;
  justify-content: center;
}

/* アップロードコンテナ */
.img-upload {
  width: 100%;
  max-width: 480px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* ドロップエリア(labelをボタン代わりに使う) */
.img-upload__area {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 40px 24px;
  border: 2px dashed var(--upload-border);
  border-radius: 12px;
  background: var(--upload-bg);
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
}

.img-upload__area:hover,
.img-upload__area.is-dragover {
  border-color: var(--upload-border-hover);
  background: var(--upload-bg-dragover);
}

/* アップロードアイコン */
.img-upload__icon {
  width: 40px;
  height: 40px;
  color: var(--upload-icon-color);
}

.img-upload__text {
  font-size: 14px;
  color: var(--upload-text-color);
  font-weight: 600;
  text-align: center;
}

.img-upload__sub {
  font-size: 12px;
  color: var(--upload-sub-color);
}

/* input は非表示にしてlabelでトリガーする */
.img-upload__input {
  display: none;
}

/* プレビューエリア */
.img-upload__preview {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 20px;
  border: 1px solid var(--upload-border);
  border-radius: 12px;
  background: #fff;
}

.img-upload__img {
  max-width: 100%;
  max-height: 280px;
  object-fit: contain;
  border-radius: 6px;
}

.img-upload__info {
  font-size: 13px;
  color: var(--upload-text-color);
  margin: 0;
  text-align: center;
}

/* ダウンロードボタン */
.img-upload__download {
  display: inline-block;
  padding: 8px 20px;
  font-size: 13px;
  font-weight: 600;
  color: #fff;
  background: #2B7FE8;
  border-radius: 6px;
  text-decoration: none;
  transition: background 0.15s;
}

.img-upload__download:hover {
  background: #1a6fd4;
}

/* 擬似デモ注記 */
.img-upload__note {
  font-size: 12px;
  color: var(--upload-note-color);
  margin: 0;
  text-align: center;
}

@media (max-width: 480px) {
  .img-upload__area { padding: 28px 16px; }
}
// 画像アップロード&プレビュー — script.js
var uploadArea  = document.getElementById('upload-area');
var imageInput  = document.getElementById('image-input');
var previewArea = document.getElementById('preview-area');
var previewImg  = document.getElementById('preview-img');
var fileInfo    = document.getElementById('file-info');
var downloadBtn = document.getElementById('download-btn');

// ファイル選択ダイアログから選んだとき
imageInput.addEventListener('change', function () {
  if (imageInput.files.length > 0) {
    handleFile(imageInput.files[0]);
  }
});

// ドラッグ中はエリアをハイライトする
uploadArea.addEventListener('dragover', function (e) {
  e.preventDefault();
  uploadArea.classList.add('is-dragover');
});

uploadArea.addEventListener('dragleave', function () {
  uploadArea.classList.remove('is-dragover');
});

// ドロップ時にファイルを受け取る
uploadArea.addEventListener('drop', function (e) {
  e.preventDefault();
  uploadArea.classList.remove('is-dragover');
  var file = e.dataTransfer.files[0];
  if (file) { handleFile(file); }
});

function handleFile(file) {
  // 画像以外は受け付けない
  if (!file.type.startsWith('image/')) {
    alert('画像ファイル(PNG / JPG / GIF / WebP)を選択してください。');
    return;
  }

  // FileReader でプレビュー用Base64を生成する
  var reader = new FileReader();
  reader.onload = function (e) {
    previewImg.src = e.target.result;
  };
  reader.readAsDataURL(file);

  // ファイル名・サイズを表示する
  var sizeKb = (file.size / 1024).toFixed(1);
  fileInfo.textContent = file.name + '  (' + sizeKb + ' KB)';

  // ダウンロードボタンにBlobURLをセットする(加工なし・元ファイル名のまま)
  var blobUrl = URL.createObjectURL(file);
  downloadBtn.href = blobUrl;
  downloadBtn.download = file.name;

  // プレビューエリアを表示する
  previewArea.hidden = false;

  // --- サーバーに送信する場合 ---
  // var formData = new FormData();
  // formData.append('image', file);
  // var res = await fetch('/api/upload', { method: 'POST', body: formData });
  // var data = await res.json();
  // console.log('アップロード完了:', data);
}

function resetDemo() {
  imageInput.value = '';
  previewImg.src = '';
  fileInfo.textContent = '';
  downloadBtn.href = '';
  previewArea.hidden = true;
}

AI用プロンプト

このプロンプトをChatGPTやClaudeに渡すと、同様のコンポーネントをゼロから生成・カスタマイズできます。

※ このプロンプトを使ってもデモとまったく同じ動作にならない場合があります。AIの解釈や生成タイミングによって差が出ることをご了承ください。

💡 jQuery・Vue・React など特定のライブラリで実装したい場合は、プロンプトの末尾に「〇〇を使って実装してください」と追記してください。

# 画像アップロード&プレビュー 作成依頼

## 概要
画像ファイルを選択またはドラッグ&ドロップで受け取り、
サーバー送信前にブラウザ上でプレビュー表示するUIを実装してください。

## 要件
- クリックでファイル選択ダイアログを開けること
- エリアへのドラッグ&ドロップでも画像を読み込めること
- 選択した画像をページ内にプレビュー表示すること
- ファイル名とファイルサイズ(KB)を表示すること
- プレビュー表示後にダウンロードボタンを表示し、選択したファイルをそのまま(加工なし)ダウンロードできること
- デモエリア内に「このデモはサーバーへの送信を行いません」という注記を表示すること
- 画像以外のファイルが選択された場合はエラーメッセージを表示すること
- リセットボタンで初期状態に戻せること

## 技術仕様
- HTML / CSS / バニラJavaScript で実装
- 外部ライブラリ:なし
- FileReader API を使用してブラウザ上で画像を読み込む
- レスポンシブ対応:必要

## 動作詳細
FileReader の readAsDataURL() でファイルをBase64に変換し、img タグの src にセットする。
ドラッグ&ドロップは dragover / dragleave / drop イベントで実装し、dragover では e.preventDefault() でブラウザデフォルトの動作を抑制する。
MIMEタイプ(file.type)が "image/" で始まることを確認し、画像以外は受け付けない。
ダウンロードボタンは URL.createObjectURL(file) で Blob URL を生成し、a タグの href と download 属性にセットする。
サーバーに送信する場合の実装はコメントアウトで記載すること(FormData + fetch を使った例)。

## 出力形式
HTML・CSS・JavaScriptを分けて出力してください。
各ファイルは単独でコピー&ペーストして使えるよう記述してください。