アバターアップロード
丸いプロフィール画像にカーソルを乗せるとカメラアイコンが浮かび上がるアップロード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);
});
})();
🤖 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で提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。