追従ミニプレーヤー
画面下に固定された小型のメディアプレーヤー。カバー画像・タイトル・再生/一時停止・進捗バー・波形イコライザを備えます。ポッドキャストや音楽、音声記事のサイトで継続再生を支えます。
ライブデモ
使用例(お題: アイドルグループ Sakura)
この技法を「アイドルグループ Sakura」というテーマのダミーサイトで実際に使った例です。
HTML
<!-- Sakura:楽曲の追従ミニプレーヤー -->
<div class="smp-frame">
<div class="smp-stage">
<h1>NOW PLAYING</h1>
<p>下部に固定された小型プレーヤーで、続きから再生。</p>
</div>
<div class="smp-player">
<span class="smp-cover" aria-hidden="true"><span class="smp-cover__moon"></span></span>
<div class="smp-meta">
<b>星降る夜の桜物語</b>
<small>Sakura / 2nd Single</small>
</div>
<div class="smp-eq" id="smpEq" aria-hidden="true"><i></i><i></i><i></i><i></i></div>
<button class="smp-play" id="smpPlay" type="button" aria-label="一時停止">
<span class="smp-play__pause" aria-hidden="true"></span>
<span class="smp-play__tri" aria-hidden="true"></span>
</button>
<div class="smp-progress"><span class="smp-fill" id="smpFill"></span></div>
<span class="smp-time" id="smpTime">0:00</span>
</div>
</div>
CSS
/* Sakura(アイドル):追従ミニプレーヤーの再スキン */
* { box-sizing: border-box; }
body { margin: 0; font-family: "Segoe UI", system-ui, -apple-system, sans-serif; }
.smp-frame { position: relative; width: 100%; height: 380px; overflow: hidden; background: linear-gradient(160deg, #2a0e2e, #160814); }
.smp-stage { display: grid; place-content: center; height: 100%; text-align: center; color: #f6e3ec; padding: 0 24px; }
.smp-stage h1 { margin: 0; font-size: 26px; font-weight: 800; letter-spacing: .1em; }
.smp-stage p { margin: 12px 0 0; font-size: 13px; line-height: 1.8; color: #c79bb2; }
.smp-player { position: absolute; left: 16px; right: 16px; bottom: 16px; z-index: 10; display: flex; align-items: center; gap: 14px; padding: 12px 16px; border-radius: 16px; background: rgba(52,18,42,.85); -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px); border: 1px solid rgba(255,255,255,.14); box-shadow: 0 18px 40px rgba(0,0,0,.4); color: #fff; }
.smp-cover { width: 46px; height: 46px; border-radius: 11px; flex: 0 0 auto; background: linear-gradient(135deg, #d6336c, #f76707); display: grid; place-items: center; }
.smp-cover__moon { width: 18px; height: 18px; border-radius: 50%; background: #ffe3a0; box-shadow: inset -5px -2px 0 0 rgba(0,0,0,.22); }
.smp-meta { min-width: 0; display: flex; flex-direction: column; line-height: 1.3; }
.smp-meta b { font-size: 13.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.smp-meta small { font-size: 11px; color: #e3a9c6; }
.smp-eq { display: flex; align-items: flex-end; gap: 3px; height: 22px; margin-left: auto; }
.smp-eq i { width: 3px; background: #f8a5cf; border-radius: 2px; animation: smp-eq 1s ease-in-out infinite; }
.smp-eq i:nth-child(1) { height: 40%; animation-delay: -.2s; }
.smp-eq i:nth-child(2) { height: 90%; animation-delay: -.5s; }
.smp-eq i:nth-child(3) { height: 60%; animation-delay: -.1s; }
.smp-eq i:nth-child(4) { height: 75%; animation-delay: -.7s; }
@keyframes smp-eq { 0%,100% { transform: scaleY(.4); } 50% { transform: scaleY(1); } }
.smp-player.is-paused .smp-eq i { animation-play-state: paused; }
.smp-play { flex: 0 0 auto; width: 40px; height: 40px; border-radius: 50%; border: none; cursor: pointer; background: #fff; display: grid; place-items: center; transition: transform .15s ease; }
.smp-play:hover { transform: scale(1.06); }
.smp-play__pause { grid-area: 1/1; width: 12px; height: 13px; border-left: 4px solid #2a0e2e; border-right: 4px solid #2a0e2e; }
.smp-play__tri { grid-area: 1/1; width: 0; height: 0; border-left: 13px solid #2a0e2e; border-top: 8px solid transparent; border-bottom: 8px solid transparent; margin-left: 3px; display: none; }
.smp-player.is-paused .smp-play__pause { display: none; }
.smp-player.is-paused .smp-play__tri { display: block; }
.smp-progress { flex: 1.4; min-width: 60px; height: 5px; border-radius: 3px; background: rgba(255,255,255,.18); overflow: hidden; }
.smp-fill { display: block; height: 100%; width: 0%; background: linear-gradient(90deg, #f8a5cf, #ffd9a0); border-radius: 3px; }
.smp-time { font-size: 11px; color: #e3a9c6; font-variant-numeric: tabular-nums; flex: 0 0 auto; }
@media (prefers-reduced-motion: reduce) { .smp-eq i { animation: none; } .smp-play { transition: none; } }
JavaScript
// (デモと同じフックを流用)進捗バーを進め、再生/一時停止でイコライザ連動
(() => {
const player = document.querySelector('.smp-player');
const fill = document.getElementById('smpFill');
const time = document.getElementById('smpTime');
const play = document.getElementById('smpPlay');
if (!player || !fill || !play) return;
const TOTAL = 222;
let sec = 0;
let playing = !matchMedia('(prefers-reduced-motion: reduce)').matches;
player.classList.toggle('is-paused', !playing);
const fmt = (s) => `${Math.floor(s / 60)}:${String(Math.floor(s % 60)).padStart(2, '0')}`;
function frame() { fill.style.width = (sec / TOTAL * 100).toFixed(1) + '%'; if (time) time.textContent = fmt(sec); }
frame();
play.addEventListener('click', () => { playing = !playing; player.classList.toggle('is-paused', !playing); play.setAttribute('aria-label', playing ? '一時停止' : '再生'); });
setInterval(() => { if (!playing) return; sec = (sec + 1) % (TOTAL + 1); frame(); }, 1000);
})();
実装ガイド
使いどころ
ポッドキャスト・音楽・音声記事のサイトに。画面下に固定された小型プレーヤーで、ページ移動しても継続再生を支えます。
実装時の注意点
進捗バーを毎秒進め、再生/一時停止でイコライザのアニメーションと連動(animation-play-state)。再生ボタンは■↔▶でCSS切替。カバー・タイトル・時間表示を1行にまとめています。
対応ブラウザ
CSS アニメーション・flexbox は全モダンブラウザ対応。実音声は <audio> と接続し、進捗を timeupdate で反映します。
よくある失敗
自動再生は多くのブラウザでブロックされるため、ユーザー操作起点で再生を。実装では <audio> の currentTime/duration と同期し、シーク(バークリック)にも対応を。モバイルのロック画面メディア制御(Media Session API)対応も検討します。
応用例
プレイリスト送り、再生速度変更、シークバーのドラッグ、Media Session 連携、波形のリアルタイム表示などに展開できます。
コード
HTML
<!-- 画面下に固定された追従ミニプレーヤー -->
<div class="smp-frame">
<div class="smp-stage">
<h1>追従ミニプレーヤー</h1>
<p>下部に固定された小型プレーヤー。<br>再生・進捗・イコライザが動きます。</p>
</div>
<div class="smp-player">
<span class="smp-cover" aria-hidden="true"><span class="smp-cover__moon"></span></span>
<div class="smp-meta">
<b>夜更けのララバイ</b>
<small>Moon Brew Sessions</small>
</div>
<div class="smp-eq" id="smpEq" aria-hidden="true"><i></i><i></i><i></i><i></i></div>
<button class="smp-play" id="smpPlay" type="button" aria-label="一時停止">
<span class="smp-play__pause" aria-hidden="true"></span>
<span class="smp-play__tri" aria-hidden="true"></span>
</button>
<div class="smp-progress"><span class="smp-fill" id="smpFill"></span></div>
<span class="smp-time" id="smpTime">0:00</span>
</div>
</div>
CSS
* { box-sizing: border-box; }
body { margin: 0; font-family: "Segoe UI", system-ui, -apple-system, sans-serif; }
.smp-frame { position: relative; width: 100%; height: 380px; overflow: hidden; background: linear-gradient(160deg, #1a1230, #0c0a18); }
.smp-stage { display: grid; place-content: center; height: 100%; text-align: center; color: #e8e3f5; padding: 0 24px; }
.smp-stage h1 { margin: 0; font-size: 26px; font-weight: 800; }
.smp-stage p { margin: 12px 0 0; font-size: 13px; line-height: 1.8; color: #a99fce; }
.smp-player {
position: absolute; left: 16px; right: 16px; bottom: 16px; z-index: 10;
display: flex; align-items: center; gap: 14px;
padding: 12px 16px; border-radius: 16px;
background: rgba(28,22,52,.85); -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
border: 1px solid rgba(255,255,255,.12); box-shadow: 0 18px 40px rgba(0,0,0,.4);
color: #fff;
}
.smp-cover { width: 46px; height: 46px; border-radius: 11px; flex: 0 0 auto; background: linear-gradient(135deg, #7c3aed, #db2777); display: grid; place-items: center; }
.smp-cover__moon { width: 18px; height: 18px; border-radius: 50%; background: #fde68a; box-shadow: inset -5px -2px 0 0 rgba(0,0,0,.25); }
.smp-meta { min-width: 0; display: flex; flex-direction: column; line-height: 1.3; }
.smp-meta b { font-size: 13.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.smp-meta small { font-size: 11px; color: #b3a9d6; }
.smp-eq { display: flex; align-items: flex-end; gap: 3px; height: 22px; margin-left: auto; }
.smp-eq i { width: 3px; background: #c4b5fd; border-radius: 2px; animation: smp-eq 1s ease-in-out infinite; }
.smp-eq i:nth-child(1) { height: 40%; animation-delay: -.2s; }
.smp-eq i:nth-child(2) { height: 90%; animation-delay: -.5s; }
.smp-eq i:nth-child(3) { height: 60%; animation-delay: -.1s; }
.smp-eq i:nth-child(4) { height: 75%; animation-delay: -.7s; }
@keyframes smp-eq { 0%,100% { transform: scaleY(.4); } 50% { transform: scaleY(1); } }
.smp-player.is-paused .smp-eq i { animation-play-state: paused; }
.smp-play { flex: 0 0 auto; width: 40px; height: 40px; border-radius: 50%; border: none; cursor: pointer; background: #fff; display: grid; place-items: center; transition: transform .15s ease; }
.smp-play:hover { transform: scale(1.06); }
.smp-play__pause { grid-area: 1/1; width: 12px; height: 13px; border-left: 4px solid #1a1230; border-right: 4px solid #1a1230; }
.smp-play__tri { grid-area: 1/1; width: 0; height: 0; border-left: 13px solid #1a1230; border-top: 8px solid transparent; border-bottom: 8px solid transparent; margin-left: 3px; display: none; }
.smp-player.is-paused .smp-play__pause { display: none; }
.smp-player.is-paused .smp-play__tri { display: block; }
.smp-progress { flex: 1.4; min-width: 60px; height: 5px; border-radius: 3px; background: rgba(255,255,255,.18); overflow: hidden; }
.smp-fill { display: block; height: 100%; width: 0%; background: linear-gradient(90deg, #a78bfa, #f0abfc); border-radius: 3px; }
.smp-time { font-size: 11px; color: #b3a9d6; font-variant-numeric: tabular-nums; flex: 0 0 auto; }
@media (prefers-reduced-motion: reduce) { .smp-eq i { animation: none; } .smp-play { transition: none; } }
JavaScript
// 進捗バーを進め、再生/一時停止でイコライザと連動
(() => {
const player = document.querySelector('.smp-player');
const fill = document.getElementById('smpFill');
const time = document.getElementById('smpTime');
const play = document.getElementById('smpPlay');
if (!player || !fill || !play) return;
const TOTAL = 180; // 3:00(秒)
let sec = 0;
let playing = !matchMedia('(prefers-reduced-motion: reduce)').matches;
player.classList.toggle('is-paused', !playing);
const fmt = (s) => `${Math.floor(s / 60)}:${String(Math.floor(s % 60)).padStart(2, '0')}`;
function frame() {
fill.style.width = (sec / TOTAL * 100).toFixed(1) + '%';
if (time) time.textContent = fmt(sec);
}
frame();
play.addEventListener('click', () => {
playing = !playing;
player.classList.toggle('is-paused', !playing);
play.setAttribute('aria-label', playing ? '一時停止' : '再生');
});
setInterval(() => {
if (!playing) return;
sec = (sec + 1) % (TOTAL + 1);
frame();
}, 1000);
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「追従ミニプレーヤー」の効果を追加してください。
# 追加してほしい効果
追従ミニプレーヤー(追従ウィジェット)
画面下に固定された小型のメディアプレーヤー。カバー画像・タイトル・再生/一時停止・進捗バー・波形イコライザを備えます。ポッドキャストや音楽、音声記事のサイトで継続再生を支えます。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- 画面下に固定された追従ミニプレーヤー -->
<div class="smp-frame">
<div class="smp-stage">
<h1>追従ミニプレーヤー</h1>
<p>下部に固定された小型プレーヤー。<br>再生・進捗・イコライザが動きます。</p>
</div>
<div class="smp-player">
<span class="smp-cover" aria-hidden="true"><span class="smp-cover__moon"></span></span>
<div class="smp-meta">
<b>夜更けのララバイ</b>
<small>Moon Brew Sessions</small>
</div>
<div class="smp-eq" id="smpEq" aria-hidden="true"><i></i><i></i><i></i><i></i></div>
<button class="smp-play" id="smpPlay" type="button" aria-label="一時停止">
<span class="smp-play__pause" aria-hidden="true"></span>
<span class="smp-play__tri" aria-hidden="true"></span>
</button>
<div class="smp-progress"><span class="smp-fill" id="smpFill"></span></div>
<span class="smp-time" id="smpTime">0:00</span>
</div>
</div>
【CSS】
* { box-sizing: border-box; }
body { margin: 0; font-family: "Segoe UI", system-ui, -apple-system, sans-serif; }
.smp-frame { position: relative; width: 100%; height: 380px; overflow: hidden; background: linear-gradient(160deg, #1a1230, #0c0a18); }
.smp-stage { display: grid; place-content: center; height: 100%; text-align: center; color: #e8e3f5; padding: 0 24px; }
.smp-stage h1 { margin: 0; font-size: 26px; font-weight: 800; }
.smp-stage p { margin: 12px 0 0; font-size: 13px; line-height: 1.8; color: #a99fce; }
.smp-player {
position: absolute; left: 16px; right: 16px; bottom: 16px; z-index: 10;
display: flex; align-items: center; gap: 14px;
padding: 12px 16px; border-radius: 16px;
background: rgba(28,22,52,.85); -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
border: 1px solid rgba(255,255,255,.12); box-shadow: 0 18px 40px rgba(0,0,0,.4);
color: #fff;
}
.smp-cover { width: 46px; height: 46px; border-radius: 11px; flex: 0 0 auto; background: linear-gradient(135deg, #7c3aed, #db2777); display: grid; place-items: center; }
.smp-cover__moon { width: 18px; height: 18px; border-radius: 50%; background: #fde68a; box-shadow: inset -5px -2px 0 0 rgba(0,0,0,.25); }
.smp-meta { min-width: 0; display: flex; flex-direction: column; line-height: 1.3; }
.smp-meta b { font-size: 13.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.smp-meta small { font-size: 11px; color: #b3a9d6; }
.smp-eq { display: flex; align-items: flex-end; gap: 3px; height: 22px; margin-left: auto; }
.smp-eq i { width: 3px; background: #c4b5fd; border-radius: 2px; animation: smp-eq 1s ease-in-out infinite; }
.smp-eq i:nth-child(1) { height: 40%; animation-delay: -.2s; }
.smp-eq i:nth-child(2) { height: 90%; animation-delay: -.5s; }
.smp-eq i:nth-child(3) { height: 60%; animation-delay: -.1s; }
.smp-eq i:nth-child(4) { height: 75%; animation-delay: -.7s; }
@keyframes smp-eq { 0%,100% { transform: scaleY(.4); } 50% { transform: scaleY(1); } }
.smp-player.is-paused .smp-eq i { animation-play-state: paused; }
.smp-play { flex: 0 0 auto; width: 40px; height: 40px; border-radius: 50%; border: none; cursor: pointer; background: #fff; display: grid; place-items: center; transition: transform .15s ease; }
.smp-play:hover { transform: scale(1.06); }
.smp-play__pause { grid-area: 1/1; width: 12px; height: 13px; border-left: 4px solid #1a1230; border-right: 4px solid #1a1230; }
.smp-play__tri { grid-area: 1/1; width: 0; height: 0; border-left: 13px solid #1a1230; border-top: 8px solid transparent; border-bottom: 8px solid transparent; margin-left: 3px; display: none; }
.smp-player.is-paused .smp-play__pause { display: none; }
.smp-player.is-paused .smp-play__tri { display: block; }
.smp-progress { flex: 1.4; min-width: 60px; height: 5px; border-radius: 3px; background: rgba(255,255,255,.18); overflow: hidden; }
.smp-fill { display: block; height: 100%; width: 0%; background: linear-gradient(90deg, #a78bfa, #f0abfc); border-radius: 3px; }
.smp-time { font-size: 11px; color: #b3a9d6; font-variant-numeric: tabular-nums; flex: 0 0 auto; }
@media (prefers-reduced-motion: reduce) { .smp-eq i { animation: none; } .smp-play { transition: none; } }
【JavaScript】
// 進捗バーを進め、再生/一時停止でイコライザと連動
(() => {
const player = document.querySelector('.smp-player');
const fill = document.getElementById('smpFill');
const time = document.getElementById('smpTime');
const play = document.getElementById('smpPlay');
if (!player || !fill || !play) return;
const TOTAL = 180; // 3:00(秒)
let sec = 0;
let playing = !matchMedia('(prefers-reduced-motion: reduce)').matches;
player.classList.toggle('is-paused', !playing);
const fmt = (s) => `${Math.floor(s / 60)}:${String(Math.floor(s % 60)).padStart(2, '0')}`;
function frame() {
fill.style.width = (sec / TOTAL * 100).toFixed(1) + '%';
if (time) time.textContent = fmt(sec);
}
frame();
play.addEventListener('click', () => {
playing = !playing;
player.classList.toggle('is-paused', !playing);
play.setAttribute('aria-label', playing ? '一時停止' : '再生');
});
setInterval(() => {
if (!playing) return;
sec = (sec + 1) % (TOTAL + 1);
frame();
}, 1000);
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。