アバターアップロード

丸いプロフィール画像にカーソルを乗せるとカメラアイコンが浮かび上がるアップロードUI。クリックしてファイルを選ぶと、FileReaderでその場に新しい画像がプレビューされます。

#css#js#avatar#upload

ライブデモ

コード

HTML
<!-- アバターアップロード: hoverでカメラアイコン表示、クリックでファイル選択しFileReaderでプレビュー差し替え -->
<div class="stage">
  <div class="upload" role="button" tabindex="0" aria-label="クリックしてプロフィール画像を変更">
    <img id="avatarPreview" class="upload__img" src="https://picsum.photos/id/64/200/200" alt="現在のプロフィール画像" width="120" height="120">
    <span class="upload__overlay" aria-hidden="true">
      <span class="upload__icon">📷</span>
      <span class="upload__text">変更する</span>
    </span>
    <input type="file" id="avatarInput" class="upload__input" accept="image/*" hidden>
  </div>
  <p class="hint">クリックまたはEnterキーで画像を選択できます</p>
</div>
CSS
* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 28px;
  font-family: "Hiragino Sans", "Segoe UI", system-ui, -apple-system, sans-serif;
  background:
    radial-gradient(circle at 16% 12%, rgba(45, 212, 191, .16) 0%, transparent 42%),
    radial-gradient(circle at 88% 88%, rgba(56, 189, 248, .12) 0%, transparent 45%),
    #0f1615;
  color: #e6f4f1;
}

.stage {
  display: grid;
  justify-items: center;
  gap: 18px;
}

.upload {
  position: relative;
  width: 120px;
  height: 120px;
  border-radius: 50%;
  overflow: hidden;
  cursor: pointer;
  box-shadow: 0 18px 40px -16px rgba(0, 0, 0, .65);
  outline-offset: 5px;
}

.upload__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: filter .3s ease;
}

.upload__overlay {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  gap: 4px;
  color: #fff;
  background: rgba(8, 14, 13, 0);
  opacity: 0;
  transition: opacity .3s ease, background .3s ease;
}

.upload:hover .upload__overlay,
.upload:focus-visible .upload__overlay {
  opacity: 1;
  background: rgba(8, 14, 13, .55);
}
.upload:hover .upload__img,
.upload:focus-visible .upload__img {
  filter: brightness(.7);
}

.upload__icon {
  font-size: 22px;
  line-height: 1;
}
.upload__text {
  font-size: 11px;
  letter-spacing: .05em;
}

.hint {
  margin: 0;
  font-size: 12.5px;
  color: rgba(230, 244, 241, .55);
  text-align: center;
  max-width: 260px;
}

@media (prefers-reduced-motion: reduce) {
  .upload__img,
  .upload__overlay { transition: none; }
}
JavaScript
// アバターアップロード: クリック/Enterでファイル選択を開き、FileReaderで選択画像をその場にプレビュー表示する
(() => {
  const wrap = document.querySelector('.upload');
  const img = document.getElementById('avatarPreview');
  const input = document.getElementById('avatarInput');
  if (!wrap || !img || !input) return; // null安全

  const openPicker = () => input.click();

  wrap.addEventListener('click', openPicker);

  // role="button"のためキーボード操作(Enter/Space)にも対応する
  wrap.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' || e.key === ' ' || e.key === 'Spacebar') {
      e.preventDefault();
      openPicker();
    }
  });

  input.addEventListener('change', () => {
    const file = input.files && input.files[0];
    if (!file || typeof file.type !== 'string' || file.type.indexOf('image/') !== 0) return;

    const reader = new FileReader();
    reader.onload = (ev) => {
      const result = ev.target && ev.target.result;
      if (typeof result === 'string') img.src = result;
    };
    reader.readAsDataURL(file);
  });
})();

🤖 AIエージェント用プロンプト

このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「アバターアップロード」の効果を追加してください。

# 追加してほしい効果
アバターアップロード(バッジ & アバター)
丸いプロフィール画像にカーソルを乗せるとカメラアイコンが浮かび上がるアップロードUI。クリックしてファイルを選ぶと、FileReaderでその場に新しい画像がプレビューされます。

# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- アバターアップロード: hoverでカメラアイコン表示、クリックでファイル選択しFileReaderでプレビュー差し替え -->
<div class="stage">
  <div class="upload" role="button" tabindex="0" aria-label="クリックしてプロフィール画像を変更">
    <img id="avatarPreview" class="upload__img" src="https://picsum.photos/id/64/200/200" alt="現在のプロフィール画像" width="120" height="120">
    <span class="upload__overlay" aria-hidden="true">
      <span class="upload__icon">📷</span>
      <span class="upload__text">変更する</span>
    </span>
    <input type="file" id="avatarInput" class="upload__input" accept="image/*" hidden>
  </div>
  <p class="hint">クリックまたはEnterキーで画像を選択できます</p>
</div>

【CSS】
* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 28px;
  font-family: "Hiragino Sans", "Segoe UI", system-ui, -apple-system, sans-serif;
  background:
    radial-gradient(circle at 16% 12%, rgba(45, 212, 191, .16) 0%, transparent 42%),
    radial-gradient(circle at 88% 88%, rgba(56, 189, 248, .12) 0%, transparent 45%),
    #0f1615;
  color: #e6f4f1;
}

.stage {
  display: grid;
  justify-items: center;
  gap: 18px;
}

.upload {
  position: relative;
  width: 120px;
  height: 120px;
  border-radius: 50%;
  overflow: hidden;
  cursor: pointer;
  box-shadow: 0 18px 40px -16px rgba(0, 0, 0, .65);
  outline-offset: 5px;
}

.upload__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: filter .3s ease;
}

.upload__overlay {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  gap: 4px;
  color: #fff;
  background: rgba(8, 14, 13, 0);
  opacity: 0;
  transition: opacity .3s ease, background .3s ease;
}

.upload:hover .upload__overlay,
.upload:focus-visible .upload__overlay {
  opacity: 1;
  background: rgba(8, 14, 13, .55);
}
.upload:hover .upload__img,
.upload:focus-visible .upload__img {
  filter: brightness(.7);
}

.upload__icon {
  font-size: 22px;
  line-height: 1;
}
.upload__text {
  font-size: 11px;
  letter-spacing: .05em;
}

.hint {
  margin: 0;
  font-size: 12.5px;
  color: rgba(230, 244, 241, .55);
  text-align: center;
  max-width: 260px;
}

@media (prefers-reduced-motion: reduce) {
  .upload__img,
  .upload__overlay { transition: none; }
}

【JavaScript】
// アバターアップロード: クリック/Enterでファイル選択を開き、FileReaderで選択画像をその場にプレビュー表示する
(() => {
  const wrap = document.querySelector('.upload');
  const img = document.getElementById('avatarPreview');
  const input = document.getElementById('avatarInput');
  if (!wrap || !img || !input) return; // null安全

  const openPicker = () => input.click();

  wrap.addEventListener('click', openPicker);

  // role="button"のためキーボード操作(Enter/Space)にも対応する
  wrap.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' || e.key === ' ' || e.key === 'Spacebar') {
      e.preventDefault();
      openPicker();
    }
  });

  input.addEventListener('change', () => {
    const file = input.files && input.files[0];
    if (!file || typeof file.type !== 'string' || file.type.indexOf('image/') !== 0) return;

    const reader = new FileReader();
    reader.onload = (ev) => {
      const result = ev.target && ev.target.result;
      if (typeof result === 'string') img.src = result;
    };
    reader.readAsDataURL(file);
  });
})();

# 外部ライブラリ
なし(追加ライブラリ不要)

# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。