Working remotely • We respond within 24 hours
Веб-разработка и дизайн
Назад к портфолио

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);
  });
})();
13.12.2025
04.03.2026
77
Admin

Комментарии (0)

Загрузка комментариев...