Hexo+Orange主题下基于APlayer+MetingJs+pjax添加音乐播放器
2024-03-17 16:18:45
#Blog
前言:
基于 Hexo 框架, Orange 主题,在页面右下角工具栏引入音乐播放器,播放网易云音乐在线个人歌单歌曲;
引入 pjax.js,实现切换页面,音乐播放器能继续播放音乐。
操作步骤:
1、在主题下的 layout/_partial 新增 music-player.ejs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| <% if (theme.music && theme.music.enable) { %> <!-- iconfont 自己导入的 music 图标,先在https://www.iconfont.cn/中找到合适的 music 图标,将图标添加到项目,再引入项目在线链接 --> <link rel="stylesheet" href="//at.alicdn.com/t/c/font_4973638_ovgeuq5516.css" />
<!-- 音乐播放器弹窗容器 --> <div class="music-player-popup hidden" id="music-player-popup"> <div class="music-player-content" id="music-player-content"> <!-- 播放器内容将在点击时动态加载 --> </div> </div>
<!-- 音乐播放器配置 --> <script> window.musicPlayerConfig = { server: "<%- theme.music.meting.server %>", type: "<%- theme.music.meting.type %>", id: "<%- theme.music.meting.id %>", fixed: "<%- theme.music.meting.fixed %>", mini: "<%- theme.music.meting.mini %>", autoplay: "<%- theme.music.meting.autoplay %>", theme: "<%- theme.music.meting.theme %>", loop: "<%- theme.music.meting.loop %>", order: "<%- theme.music.meting.order %>", preload: "<%- theme.music.meting.preload %>", volume: "<%- theme.music.meting.volume %>", lrctype: "<%- theme.music.meting.lrctype || 0 %>", listfolded: "<%- theme.music.meting.listfolded || true %>", listmaxheight: "<%- theme.music.meting.listmaxheight || 0 %>" }; </script>
<!-- 引入音乐播放器脚本 --> <script src="/js/music-player.js"></script> <!-- pjax --> <% if (theme.cdns && theme.cdns.pjax && theme.cdns.pjax.enable) { %> <script defer type="text/javascript" src="<%- theme.cdns.pjax.url %>"></script> <% } else { %> <script defer type="text/javascript" src="/plugins/jquery.pjax.min.js"></script> <% } %>
<script src="/js/pjax.js"></script>
<% } %>
|
2、在主题下的 layout/layout.ejs 引入music-player.ejs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <!DOCTYPE html> <html lang="<%= config.language %>" color-mode="light">
<%- partial('_partial/header') %>
<body> <div id="app"> <%- partial('_partial/navigation') %>
<div class="flex-container"> <%- body %> <%- partial('_partial/footer') %> </div>
<div class="tools-bar"> <%- partial('_partial/backtotop') %>
<%- partial('_partial/search') %>
<%- partial('_partial/colorscheme') %> <!-- 音乐播放器图标 --> <% if (theme.music && theme.music.enable) { %> <div class="music-icon tools-bar-item" id="music-icon"> <a href="javascript: void(0)"> <i class="iconfont icon-music"></i> </a> </div> <% } %>
<%- partial('_partial/shares') %> </div> </div>
<!-- 音乐播放器弹窗放在PJAX容器外部,保持播放状态 --> <%- partial('_partial/music-player') %> </body> </html>
|
3、在主题下的 source/css 新增 music-player.css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
| .music-icon { position: relative; }
.music-player-popup { position: fixed; bottom: 110px; right: 15px; width: 280px; max-width: calc(100vw - 30px); background: var(--bg-body); border: 1px solid var(--color-divider-md-border); border-radius: 12px; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); z-index: 998; opacity: 0; transform: translateY(8px); transition: all 0.2s ease-out; }
.music-player-popup.hidden { display: none; }
.music-player-popup.show { opacity: 1; transform: translateY(0); }
.music-player-content { padding: 7px; background: var(--bg-body); border-radius: 12px; overflow: hidden; }
.music-player-content meting-js .aplayer { background: var(--bg-body); border-radius: 12px; box-shadow: none; margin: 0; font-size: 13px; border: none; }
.music-player-content meting-js .aplayer .aplayer-body { padding: 1px; }
.music-player-content meting-js .aplayer .aplayer-info { padding: 2px; height: 65px; margin-left: 65px; }
.music-player-content meting-js .aplayer .aplayer-pic { width: 60px; height: 60px; border-radius: 12px; }
.music-player-content meting-js .aplayer .aplayer-info .aplayer-music { padding-top: 5px; }
.music-player-content meting-js .aplayer .aplayer-info .aplayer-music .aplayer-title { font-size: 13px; font-weight: 500; line-height: 1.3; margin-bottom: 2px; }
.music-player-content meting-js .aplayer .aplayer-info .aplayer-music .aplayer-author { font-size: 11px; line-height: 1.2; opacity: 0.7; }
.music-player-content meting-js .aplayer .aplayer-controller { margin-top: 20px; }
.music-player-content meting-js .aplayer .aplayer-controller .aplayer-play { }
.music-player-content meting-js .aplayer .aplayer-controller .aplayer-bar-wrap { }
.music-player-content meting-js .aplayer .aplayer-controller .aplayer-time { }
.music-player-content meting-js .aplayer .aplayer-list { max-height: 200px; overflow-y: auto; background: var(--bg-body); border-radius: 0 0 12px 12px; transition: max-height 0.3s ease; }
.music-player-content meting-js .aplayer:not(.aplayer-withlist) .aplayer-list { max-height: 0; overflow: hidden; }
.music-player-content meting-js .aplayer .aplayer-lrc { display: none !important; }
.music-player-content meting-js .aplayer .aplayer-lrc-button { display: none !important; }
.music-player-content meting-js { border-radius: 12px; overflow: hidden; display: block; }
@media (max-width: 768px) { .music-player-popup { width: 260px; bottom: 80px; right: 12px; } }
@media (max-width: 480px) { .music-player-popup { width: calc(100vw - 24px); bottom: 75px; right: 12px; left: 12px; } }
@media (max-width: 360px) { .music-player-popup { width: calc(100vw - 20px); bottom: 70px; right: 10px; left: 10px; } }
:root[color-mode="dark"] .music-player-popup { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); border-color: rgba(255, 255, 255, 0.1); }
.music-player-content meting-js .aplayer .aplayer-list::-webkit-scrollbar { width: 3px; }
.music-player-content meting-js .aplayer .aplayer-list::-webkit-scrollbar-track { background: transparent; }
.music-player-content meting-js .aplayer .aplayer-list::-webkit-scrollbar-thumb { background: rgba(0, 0, 0, 0.2); border-radius: 2px; }
.music-player-content meting-js .aplayer .aplayer-list::-webkit-scrollbar-thumb:hover { background: rgba(0, 0, 0, 0.3); }
:root[color-mode="dark"] .music-player-content meting-js .aplayer .aplayer-list::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2); }
:root[color-mode="dark"] .music-player-content meting-js .aplayer .aplayer-list::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.3); }
|
4、在主题下的 source/js 新增 music-player.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
| let musicPlayerLoaded = localStorage.getItem('musicPlayerLoaded') === 'true'; let musicIcon, musicPlayerPopup;
document.addEventListener('DOMContentLoaded', function() { musicIcon = document.querySelector('#music-icon'); musicPlayerPopup = document.querySelector('#music-player-popup');
if (!musicIcon || !musicPlayerPopup) return;
if (musicPlayerLoaded) { initializeLoadedPlayer(); }
bindEvents(); });
function bindEvents() { musicIcon.addEventListener('click', handleIconClick);
document.addEventListener('click', handleOutsideClick);
musicPlayerPopup.addEventListener('click', e => e.stopPropagation());
document.addEventListener('keydown', handleKeydown); }
function handleIconClick(e) { e.preventDefault(); e.stopPropagation();
if (!musicPlayerLoaded) { loadMusicPlayer(); } else { toggleMusicPlayer(); } }
function handleOutsideClick(e) { if (musicPlayerLoaded && !musicPlayerPopup.contains(e.target) && !musicIcon.contains(e.target)) { hideMusicPlayer(); } }
function handleKeydown(e) { if (e.key === 'Escape' && musicPlayerLoaded && !musicPlayerPopup.classList.contains('hidden')) { hideMusicPlayer(); } }
function loadMusicPlayer() { loadCSS('/css/music-player.css'); loadCSS('https://cdn.jsdelivr.net/npm/aplayer@1.10.1/dist/APlayer.min.css');
loadScript('https://cdn.jsdelivr.net/npm/aplayer@1.10.1/dist/APlayer.min.js') .then(() => loadScript('https://cdn.jsdelivr.net/npm/meting@2/dist/Meting.min.js')) .then(() => { createMusicPlayer(); musicPlayerLoaded = true; localStorage.setItem('musicPlayerLoaded', 'true'); showMusicPlayer(); }) .catch(error => console.error('音乐播放器加载失败:', error)); }
function initializeLoadedPlayer() { loadCSS('/css/music-player.css'); loadCSS('https://cdn.jsdelivr.net/npm/aplayer@1.10.1/dist/APlayer.min.css');
Promise.all([ ensureScriptLoaded('https://cdn.jsdelivr.net/npm/aplayer@1.10.1/dist/APlayer.min.js'), ensureScriptLoaded('https://cdn.jsdelivr.net/npm/meting@2/dist/Meting.min.js') ]).then(() => createMusicPlayer()); }
function ensureScriptLoaded(src) { return document.querySelector(`script[src="${src}"]`) ? Promise.resolve() : loadScript(src); }
function createMusicPlayer() { const content = document.getElementById('music-player-content'); const config = window.musicPlayerConfig || {};
content.innerHTML = ` <meting-js server="${config.server || 'netease'}" type="${config.type || 'playlist'}" id="${config.id || '60198'}" fixed="${config.fixed || 'false'}" mini="${config.mini || 'false'}" autoplay="${config.autoplay || 'false'}" theme="${config.theme || '#808080'}" loop="${config.loop || 'all'}" order="${config.order || 'list'}" preload="${config.preload || 'auto'}" volume="${config.volume || '0.7'}" lrc-type="${config.lrctype || '0'}" list-folded="${config.listfolded || 'true'}" list-max-height="${config.listmaxheight || '0'}"> </meting-js> `; }
function loadCSS(href) { if (document.querySelector(`link[href="${href}"]`)) return;
const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = href; document.head.appendChild(link); }
function loadScript(src) { return new Promise((resolve, reject) => { if (document.querySelector(`script[src="${src}"]`)) { resolve(); return; }
const script = document.createElement('script'); script.src = src; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); }
function showMusicPlayer() { musicPlayerPopup.classList.remove('hidden'); requestAnimationFrame(() => { musicPlayerPopup.classList.add('show'); }); }
function toggleMusicPlayer() { const isHidden = musicPlayerPopup.classList.contains('hidden'); isHidden ? showMusicPlayer() : hideMusicPlayer(); }
function hideMusicPlayer() { musicPlayerPopup.classList.remove('show'); setTimeout(() => { musicPlayerPopup.classList.add('hidden'); }, 200); }
|
5、引入 jquery.pjax.min.js
访问https://cdn.jsdelivr.net/npm/jquery-pjax@2.0.1/jquery.pjax.min.js,复制全部代码,在主题下的source/plugins 下新建jquery.pjax.min.js 文件,将复制的代码粘贴进去并保存。
6、在主题下的 source/js 新增 pjax.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| function initPJAX() { if (typeof window.$ === 'undefined' || !window.$.fn.pjax) { setTimeout(initPJAX, 1000); return; }
$(document).pjax('a[href^="/"]:not([href*="#"]):not([no-pjax]):not([target="_blank"])', '#app', { fragment: '#app', timeout: 8000, });
$(document).on('pjax:end', rebindMusicIcon); }
function rebindMusicIcon() { const musicIcon = document.querySelector('#music-icon'); const musicPlayerPopup = document.querySelector('#music-player-popup');
if (!musicIcon || !musicPlayerPopup) return;
if (window.musicIconClickHandler) { musicIcon.removeEventListener('click', window.musicIconClickHandler); }
window.musicIconClickHandler = function(e) { e.preventDefault(); e.stopPropagation();
if (localStorage.getItem('musicPlayerLoaded') === 'true') { togglePlayerDisplay(musicPlayerPopup); } };
musicIcon.addEventListener('click', window.musicIconClickHandler); }
function togglePlayerDisplay(popup) { const isHidden = popup.classList.contains('hidden'); if (isHidden) { popup.classList.remove('hidden'); requestAnimationFrame(() => popup.classList.add('show')); } else { popup.classList.remove('show'); setTimeout(() => popup.classList.add('hidden'), 200); } }
['DOMContentLoaded', 'load'].forEach(event => { document.addEventListener(event, () => setTimeout(initPJAX, event === 'load' ? 1000 : 2000)); });
if (document.readyState === 'complete') { setTimeout(initPJAX, 500); }
|
7、修改主题下的_config.yml 配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| cdns: jquery: enable: false url: https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js pjax: enable: false url: https://cdn.jsdelivr.net/npm/jquery-pjax@2.0.1/jquery.pjax.min.js
music: enable: true meting: server: netease type: playlist id: 13970277026 fixed: false mini: false autoplay: false theme: '#808080' loop: all order: list preload: auto volume: 0.7 lrctype: 0 listfolded: false listmaxheight: 200
|
最终效果:

相关知识:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
$(document).pjax('a[href^="/"]:not([href*="#"]):not([no-pjax]):not([target="_blank"])', '#app', { fragment: '#app', timeout: 8000, });
$(document).on('pjax:end', rebindMusicIcon);
|
TODO:
1、基本效果已实现,不过使用pjax后,切换页面时,控制台会输出报错:

2、代码待优化,如脚本的引入可能存在重复或者放在了不合适的地方等问题。
2024-03-17 16:18:45
#Blog