Назад к портфолио
HTML: мини-плеер внутри каждого поста
олрлдофр длодлолдо рд лъ рждлжф ыждфы
×
О проекте
245574
Html
<div class="mini-audio-player"
data-track-id="123"
data-title="Название трека"
data-url="/uploads/audio/track-123.mp3">
<button class="mini-play-btn" type="button" aria-label="Play/Pause">
<!-- ICON: play -->
<svg class="icon-play" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M8 5v14l11-7z"></path>
</svg>
<!-- ICON: pause -->
<svg class="icon-pause" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" style="display:none">
<path d="M6 5h4v14H6zm8 0h4v14h-4z"></path>
</svg>
</button>
<div class="mini-audio-info">
<div class="mini-track-title">Название трека</div>
<div class="mini-audio-time"><span class="mini-time">0:00</span> / <span class="mini-duration">--:--</span></div>
</div>
</div>
Plaintext
3) HTML: нижний плеер (один раз на странице)
В самом низу feed.php перед </body> вставь:
Html
<div class="global-audio-player" id="global-audio-player" style="display:none;">
<div class="global-player-container">
<button class="global-player-btn" id="btn-back10" type="button" title="-10 сек" aria-label="Rewind 10 seconds">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M11 19l-7-7 7-7v14zm1-14h8v14h-8V5z"/></svg>
</button>
<button class="global-player-btn global-btn-play" id="btn-play" type="button" aria-label="Play/Pause">
<svg id="g-icon-play" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>
<svg id="g-icon-pause" viewBox="0 0 24 24" fill="currentColor" style="display:none"><path d="M6 5h4v14H6zm8 0h4v14h-4z"/></svg>
</button>
<button class="global-player-btn" id="btn-forward10" type="button" title="+10 сек" aria-label="Forward 10 seconds">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M13 5l7 7-7 7V5zM4 5h8v14H4V5z"/></svg>
</button>
<div class="global-player-info">
<div class="global-track-title" id="global-title">Трек не выбран</div>
<div class="global-player-progress-wrapper">
<div class="global-player-time" id="global-time">0:00 / 0:00</div>
<div class="global-progress-bar" id="progress-bar" role="slider" aria-label="Seek">
<div class="global-progress-fill" id="progress-fill"></div>
<div class="global-progress-handle" id="progress-handle"></div>
</div>
</div>
</div>
<div class="global-player-extra">
<a class="global-player-btn" id="btn-download" href="#" download title="Скачать" aria-label="Download">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 3v10l4-4 1.4 1.4L12 16.8 6.6 10.4 8 9l4 4V3h0zM5 19h14v2H5z"/></svg>
</a>
<div class="global-volume-control">
<svg viewBox="0 0 24 24" fill="currentColor" style="width:18px;height:18px;"><path d="M3 10v4h4l5 4V6L7 10H3z"/></svg>
<input class="global-volume-slider" id="volume" type="range" min="0" max="1" step="0.01" value="0.9" aria-label="Volume">
</div>
<button class="global-player-btn" id="btn-close" type="button" title="Закрыть" aria-label="Close player">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.3 5.7L12 12l6.3 6.3-1.4 1.4L10.6 13.4 4.3 19.7 2.9 18.3 9.2 12 2.9 5.7 4.3 4.3l6.3 6.3 6.3-6.3z"/></svg>
</button>
</div>
</div>
<!-- один audio на страницу -->
<audio id="global-audio" preload="metadata"></audio>
</div>
Plaintext
4) CSS: сохрани как /assets/css/audio-player.css
(фиолетовый без градиента, как кнопка “Лента”. если у тебя цвет другой — поменяй --player-purple)
Css
:root{
--player-purple: #6d28d9;
--player-purple-2: #5b21b6;
--player-white: rgba(255,255,255,.92);
--player-white-2: rgba(255,255,255,.75);
}
/* ===== Mini player ===== */
.mini-audio-player{
display:flex;
align-items:center;
gap:12px;
padding:10px 12px;
border-radius:14px;
background:#fff;
border:1px solid rgba(109,40,217,.10);
box-shadow:0 6px 18px rgba(15,23,42,.06);
transition:transform .15s ease, box-shadow .15s ease, border-color .15s ease;
}
.mini-audio-player:hover{
transform:translateY(-1px);
border-color:rgba(109,40,217,.22);
box-shadow:0 10px 26px rgba(15,23,42,.08);
}
.mini-audio-player.playing{ border-color:rgba(109,40,217,.35); }
.mini-play-btn{
width:42px;height:42px;border-radius:999px;border:0;
background:var(--player-purple); color:var(--player-white);
display:grid;place-items:center;
box-shadow:0 10px 22px rgba(109,40,217,.28);
cursor:pointer;
}
.mini-play-btn:hover{ background:var(--player-purple-2); }
.mini-play-btn svg{ width:18px;height:18px; }
.mini-audio-info{ min-width:0; flex:1; }
.mini-track-title{
font-weight:600;font-size:14px;line-height:1.25;color:#0f172a;
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.mini-audio-time{ margin-top:2px;font-size:12px;color:rgba(15,23,42,.60); }
/* ===== Global player ===== */
body.has-global-player{ padding-bottom: 92px; }
@media (max-width: 520px){ body.has-global-player{ padding-bottom: 118px; } }
.global-audio-player{
position:fixed;left:0;right:0;bottom:0;z-index:9999;
background:transparent;
padding:10px 12px 12px;
}
.global-player-container{
max-width:1100px;margin:0 auto;
background:var(--player-purple);
color:var(--player-white);
border-radius:18px;
box-shadow:0 18px 50px rgba(2,6,23,.22);
border:1px solid rgba(255,255,255,.10);
display:grid;
grid-template-columns:auto auto auto 1fr auto;
align-items:center;
gap:12px;
padding:12px 14px;
}
.global-player-btn{
width:42px;height:42px;border-radius:999px;
border:1px solid rgba(255,255,255,.18);
background:rgba(255,255,255,.10);
color:var(--player-white);
display:grid;place-items:center;
cursor:pointer;
transition:transform .12s ease, background .12s ease, border-color .12s ease;
}
.global-player-btn:hover{
background:rgba(255,255,255,.16);
border-color:rgba(255,255,255,.26);
transform:translateY(-1px);
}
.global-player-btn svg{ width:18px;height:18px; }
.global-btn-play{ width:54px;height:54px;background:rgba(255,255,255,.18);border-color:rgba(255,255,255,.30); }
.global-btn-play svg{ width:22px;height:22px; }
.global-player-info{ min-width:0; }
.global-track-title{
font-weight:700;font-size:14px;line-height:1.2;
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.global-player-progress-wrapper{
margin-top:6px;
display:grid;
grid-template-columns:auto 1fr;
gap:10px;align-items:center;
}
.global-player-time{ font-size:12px;color:var(--player-white-2);white-space:nowrap; }
.global-progress-bar{
position:relative;height:8px;border-radius:999px;
background:rgba(255,255,255,.18);
cursor:pointer;overflow:hidden;
}
.global-progress-fill{
height:100%;width:0%;
border-radius:999px;
background:rgba(255,255,255,.85);
}
.global-progress-handle{
position:absolute;top:50%;
transform:translate(-50%,-50%);
left:0%;
width:14px;height:14px;border-radius:999px;
background:#fff;
box-shadow:0 8px 20px rgba(0,0,0,.25);
opacity:0;transition:opacity .12s ease;
}
.global-progress-bar:hover .global-progress-handle{ opacity:1; }
.global-player-extra{ display:flex; align-items:center; gap:10px; }
.global-volume-control{ display:flex; align-items:center; gap:8px; }
.global-volume-slider{ width:110px; accent-color:#fff; }
@media (max-width: 720px){
.global-player-container{ grid-template-columns:auto auto auto 1fr; }
.global-player-extra{ grid-column:1 / -1; justify-content:flex-end; }
.global-volume-slider{ width:140px; }
}
Plaintext
5) JS: сохрани как /assets/js/audio-player.js
Javascript
(() => {
const audio = document.getElementById('global-audio');
const globalWrap = document.getElementById('global-audio-player');
if (!audio || !globalWrap) return;
const btnPlay = document.getElementById('btn-play');
const btnBack10 = document.getElementById('btn-back10');
const btnForward10 = document.getElementById('btn-forward10');
const btnClose = document.getElementById('btn-close');
const btnDownload = document.getElementById('btn-download');
const iconPlay = document.getElementById('g-icon-play');
const iconPause = document.getElementById('g-icon-pause');
const titleEl = document.getElementById('global-title');
const timeEl = document.getElementById('global-time');
const progressBar = document.getElementById('progress-bar');
const progressFill = document.getElementById('progress-fill');
const progressHandle = document.getElementById('progress-handle');
const volume = document.getElementById('volume');
let currentTrackId = null;
let isDragging = false;
function fmt(sec) {
if (!Number.isFinite(sec) || sec < 0) sec = 0;
const m = Math.floor(sec / 60);
const s = Math.floor(sec % 60);
return `${m}:${String(s).padStart(2, '0')}`;
}
function showPlayer() {
globalWrap.style.display = 'block';
document.body.classList.add('has-global-player');
}
function hidePlayer() {
globalWrap.style.display = 'none';
document.body.classList.remove('has-global-player');
}
function setGlobalIcons(playing) {
if (!iconPlay || !iconPause) return;
iconPlay.style.display = playing ? 'none' : 'block';
iconPause.style.display = playing ? 'block' : 'none';
}
function setMiniIcons(trackId, playing) {
document.querySelectorAll('.mini-audio-player').forEach(el => {
const id = el.dataset.trackId || null;
const playSvg = el.querySelector('.icon-play');
const pauseSvg = el.querySelector('.icon-pause');
const isThis = id === trackId;
el.classList.toggle('playing', isThis && playing);
if (playSvg && pauseSvg) {
playSvg.style.display = (isThis && playing) ? 'none' : 'block';
pauseSvg.style.display = (isThis && playing) ? 'block' : 'none';
}
});
}
function updateTimes() {
const cur = audio.currentTime || 0;
const dur = audio.duration || 0;
if (timeEl) timeEl.textContent = `${fmt(cur)} / ${fmt(dur)}`;
const pct = dur > 0 ? (cur / dur) * 100 : 0;
if (progressFill) progressFill.style.width = `${pct}%`;
if (progressHandle) progressHandle.style.left = `${pct}%`;
// обновляем мини-плеер времени (только активный)
if (currentTrackId) {
const active = document.querySelector(`.mini-audio-player[data-track-id="${CSS.escape(currentTrackId)}"]`);
if (active) {
const miniTime = active.querySelector('.mini-time');
const miniDur = active.querySelector('.mini-duration');
if (miniTime) miniTime.textContent = fmt(cur);
if (miniDur) miniDur.textContent = fmt(dur);
}
}
}
function loadAndPlay({ id, title, url }) {
if (!url) return;
currentTrackId = String(id);
showPlayer();
if (titleEl) titleEl.textContent = title || 'Без названия';
audio.src = url;
audio.play().catch(() => {});
setGlobalIcons(true);
setMiniIcons(currentTrackId, true);
if (btnDownload) {
btnDownload.href = url;
btnDownload.setAttribute('download', '');
}
}
function togglePlayPause() {
if (!audio.src) return;
if (audio.paused) {
audio.play().catch(() => {});
setGlobalIcons(true);
setMiniIcons(currentTrackId, true);
} else {
audio.pause();
setGlobalIcons(false);
setMiniIcons(currentTrackId, false);
}
}
// клик по мини-плееру
document.addEventListener('click', (e) => {
const el = e.target.closest('.mini-audio-player');
if (!el) return;
const id = el.dataset.trackId;
const title = el.dataset.title || el.querySelector('.mini-track-title')?.textContent?.trim();
const url = el.dataset.url;
// если тот же трек — просто toggle
if (audio.src && currentTrackId === String(id)) {
togglePlayPause();
return;
}
loadAndPlay({ id, title, url });
});
// кнопки глобального плеера
btnPlay?.addEventListener('click', togglePlayPause);
btnBack10?.addEventListener('click', () => {
audio.currentTime = Math.max(0, (audio.currentTime || 0) - 10);
});
btnForward10?.addEventListener('click', () => {
const dur = audio.duration || 0;
audio.currentTime = Math.min(dur || Infinity, (audio.currentTime || 0) + 10);
});
btnClose?.addEventListener('click', () => {
audio.pause();
audio.currentTime = 0;
setGlobalIcons(false);
setMiniIcons(currentTrackId, false);
hidePlayer();
currentTrackId = null;
});
volume?.addEventListener('input', () => {
audio.volume = Number(volume.value);
});
// прогрессбар: клик + drag
function seekByClientX(clientX) {
const rect = progressBar.getBoundingClientRect();
const x = Math.min(Math.max(clientX - rect.left, 0), rect.width);
const pct = rect.width ? (x / rect.width) : 0;
const dur = audio.duration || 0;
if (dur > 0) audio.currentTime = dur * pct;
}
progressBar?.addEventListener('mousedown', (e) => {
isDragging = true;
seekByClientX(e.clientX);
});
window.addEventListener('mousemove', (e) => {
if (!isDragging) return;
seekByClientX(e.clientX);
});
window.addEventListener('mouseup', () => { isDragging = false; });
// mobile touch
progressBar?.addEventListener('touchstart', (e) => {
isDragging = true;
seekByClientX(e.touches[0].clientX);
}, { passive: true });
window.addEventListener('touchmove', (e) => {
if (!isDragging) return;
seekByClientX(e.touches[0].clientX);
}, { passive: true });
window.addEventListener('touchend', () => { isDragging = false; });
// events audio
audio.addEventListener('timeupdate', () => { if (!isDragging) updateTimes(); });
audio.addEventListener('loadedmetadata', updateTimes);
audio.addEventListener('play', () => {
setGlobalIcons(true);
if (currentTrackId) setMiniIcons(currentTrackId, true);
});
audio.addEventListener('pause', () => {
setGlobalIcons(false);
if (currentTrackId) setMiniIcons(currentTrackId, false);
});
})();
Комментарии (0)