🎵 音乐胶囊模块使用说明
本仓库提供了一个可集成到 Hexo 主题 liushen 的音乐胶囊播放器模块。模块包含四个主要部分:
- 主题配置文件
- 布局模板文件
- 样式文件
- 前端脚本
本文档将详细介绍它们的放置位置、引用方式和变量配置。
1️⃣ 配置文件(根目录)
文件: _config.liushen.yml
位置: 项目根目录(blog/_config.liushen.yml),不是主题根目录。
该文件用于存放音乐模块的相关配置(如是否启用、播放列表、API 接口等)。 示例(按需调整):
# 页脚音乐胶囊
capsule:
enable: true
# 歌单 ID / 单曲 ID
id: 13597135963
# 服务商:netease / qq / xiami / kugou / baidu
server: netease
# 类型:playlist / song
type: playlist
meting_api: https://met.example.com/api?server=:server&type=:type&id=:id&r=:r
volume: 0.8
2️⃣ 布局模板文件
文件: music.pug
位置: blog/themes/liushen/layout/includes/third-party/music.pug
该文件用于渲染音乐胶囊播放器 UI,需要在 layout.pug 中手动引入。
在 blog/themes/liushen/layout/includes/layout.pug 底部 添加以下代码(保留现有内容,仅在末尾追加):
if theme.capsule && theme.capsule.enable
include ./third-party/music.pug
📌 该判断会根据
_config.liushen.yml中的capsule.enable来决定是否加载播放器。
3️⃣ 样式文件
文件: music-capsule.styl
位置: blog/themes/liushen/source/css/__layout/music-capsule.styl
该位置的styl样式表会被自动整合到main.css
💡 样式变量对照表
音乐胶囊使用了一些 CSS 变量,这些变量在亮色与暗色模式下分别定义。
🌞 亮色模式(默认)
/* nav */
--liushen-nav-bg: rgba(255, 255, 255, 0.8);
--liushen-nav-shadow: rgba(133, 133, 133, 0.6) 0px 5px 6px -5px;
/* ai_summary */
--liushen-title-font-color: #0883b7;
--liushen-maskbg: rgba(255, 255, 255, 0.85);
--liushen-ai-bg: conic-gradient(from 1.5708rad at 50% 50%, #d6b300 0%, #42A2FF 54%, #d6b300 100%);
/* card */
--liushen-card-bg: #fff;
--liushen-card-secondbg: #f1f3f8;
--liushen-card-border: 1px solid #e3e8f7;
/* button */
--liushen-button-bg: #f1f3f8;
--liushen-button-hover-bg: $theme-color;
/* text */
--liushen-text: #4c4948;
--liushen-secondtext: #3c3c43cc;
/* snackbar */
--snackbar-bg: rgba(255, 255, 255, 0.809);
--snackbar-text: #4c4948;
--snackbar-border: 1px solid rgba(126, 126, 126, 0.527);
/* menu child */
--menu-child-bg: rgba(255,255,255,0.8);
/* fancybox */
--liushen-fancybox-bg: rgba(255,255,255,0.5);
--liushen-fancybox-button-color: #000000;
🌙 暗色模式
[data-theme='dark'] {
/* nav */
--liushen-nav-bg: rgba(18,18,18,.8);
--liushen-nav-shadow: rgba(133, 133, 133, 0) 0px 5px 6px -5px;
/* ai_summary */
--liushen-maskbg: rgba(0, 0, 0, 0.85);
--liushen-ai-bg: conic-gradient(from 1.5708rad at 50% 50%,rgba(214, 178, 0, 0.46) 0%,rgba(66, 161, 255, 0.53) 54%,rgba(214, 178, 0, 0.49) 100%);
/* card */
--liushen-card-bg: #2d2d2d;
--liushen-card-secondbg: #3e3f41;
--liushen-card-border: 1px solid #42444a;
/* button */
--liushen-button-bg: #30343f;
--liushen-button-hover-bg: $theme-color;
/* text */
--liushen-text: #ffffffb3;
--liushen-secondtext: #a1a2b8;
/* snackbar */
--snackbar-bg: rgba(48, 48, 48, 0.809);
--snackbar-text: #dfdfdf;
--snackbar-border: 1px solid #cfd4dc40;
/* menu child */
--menu-child-bg: rgba(18,18,18,0.8);
/* fancybox */
--liushen-fancybox-bg: rgba(0,0,0,0.5);
--liushen-fancybox-button-color: #ffffff;
}
你可以将这些变量整合到主题的全局变量文件中,或根据配色需求自行调整。
4️⃣ 前端脚本
文件: music.js
位置: 可放在主题的任意 JS 文件中(例如 blog/themes/liushen/source/js/music.js)或合并到 main.js 中。
引入方式一(单独文件)
在主题 layout.pug 的底部或 additional-js.pug 中添加:
script(src='/js/music.js')
引入方式二(合并代码)
将 music.js 中的代码直接复制到 main.js 或其他已加载的脚本文件中。
🔗 集成流程总结
- 将
_config.liushen.yml添加到 项目根目录 并配置capsule.enable: true。 - 将
music.pug放入blog/themes/liushen/layout/includes/third-party/。 - 编辑
layout.pug,在底部添加include ./third-party/music.pug(带条件判断)。 - 将
music-capsule.styl放入blog/themes/liushen/source/css/__layout/。 - 将
music.js放入 JS 目录并在页面加载时引入,或者可以添加到任意js文件中。 - 根据需要修改 CSS 变量配色。
| 1 | # 页脚音乐胶囊 |
| 2 | capsule: |
| 3 | enable: true |
| 4 | # 歌单 ID / 单曲 ID |
| 5 | id: 13597135963 |
| 6 | # 服务商:netease / qq / xiami / kugou / baidu |
| 7 | server: netease |
| 8 | # 类型:playlist / song |
| 9 | type: playlist |
| 10 | meting_api: https://met.example.com/api?server=:server&type=:type&id=:id&r=:r |
| 11 | volume: 0.8 |
| 1 | @keyframes changeright |
| 2 | 0%, 50%, 100% |
| 3 | transform: rotate(0deg) scale(1.1) |
| 4 | box-shadow: 0 0 2px #ffffff00 |
| 5 | 25%, 75% |
| 6 | transform: rotate(90deg) scale(1.1) |
| 7 | box-shadow: 0 0 14px #ffffff |
| 8 | |
| 9 | @keyframes playingShadow |
| 10 | 0%, 100% |
| 11 | box-shadow: 0 0px 12px -3px #00000000 |
| 12 | 50% |
| 13 | box-shadow: 0 0px 12px 0px var(--default-bg-color) |
| 14 | |
| 15 | @keyframes lightBar |
| 16 | 0%, 100% |
| 17 | opacity: 0.1 |
| 18 | 60% |
| 19 | opacity: 0.3 |
| 20 | |
| 21 | .aplayer.aplayer-narrow |
| 22 | .aplayer-body, |
| 23 | .aplayer-pic |
| 24 | height: 66px |
| 25 | width: 66px |
| 26 | |
| 27 | #nav-music |
| 28 | display: flex |
| 29 | align-items: center |
| 30 | position: fixed |
| 31 | z-index: 10000 |
| 32 | bottom: 10px |
| 33 | left: 10px |
| 34 | cursor: pointer |
| 35 | transition: all 0.5s, left 0s |
| 36 | transform-origin: left bottom |
| 37 | box-shadow: var(--liushen-nav-shadow) |
| 38 | border-radius: 40px |
| 39 | overflow: hidden |
| 40 | |
| 41 | &:active |
| 42 | transform: scale(0.97) |
| 43 | |
| 44 | &.playing |
| 45 | border: var(--liushen-card-border) |
| 46 | box-shadow: 0 0px 12px -3px #00000000 |
| 47 | animation: playingShadow 5s linear infinite |
| 48 | |
| 49 | .aplayer.aplayer-withlrc |
| 50 | .aplayer-pic |
| 51 | box-shadow: 0 0 14px #ffffffa6 |
| 52 | transform: rotate(0deg) scale(1.1) |
| 53 | border-color: white |
| 54 | animation-play-state: running |
| 55 | |
| 56 | .aplayer-info |
| 57 | color: white |
| 58 | |
| 59 | #nav-music-hoverTips |
| 60 | width: 0 |
| 61 | |
| 62 | .aplayer |
| 63 | background: var(--default-bg-color) |
| 64 | border: var(--liushen-card-border) |
| 65 | backdrop-filter: saturate(180%) blur(20px) |
| 66 | transform: translateZ(0) |
| 67 | |
| 68 | .aplayer .aplayer-info .aplayer-controller .aplayer-bar-wrap .aplayer-bar .aplayer-played |
| 69 | animation-play-state: running |
| 70 | |
| 71 | &:hover:not(.playing) #nav-music-hoverTips |
| 72 | opacity: 1 |
| 73 | |
| 74 | .aplayer.aplayer-withlrc |
| 75 | .aplayer-pic |
| 76 | height: 25px |
| 77 | width: 25px |
| 78 | border-radius: 40px |
| 79 | z-index: 1 |
| 80 | transition: 0.3s |
| 81 | transform: rotate(0deg) scale(1) |
| 82 | border: var(--liushen-card-border) |
| 83 | animation: changeright 24s linear infinite |
| 84 | animation-play-state: paused |
| 85 | |
| 86 | .aplayer-info |
| 87 | height: 100% |
| 88 | color: var(--liushen-text) |
| 89 | margin: 0 |
| 90 | padding: 0 |
| 91 | display: flex |
| 92 | align-items: center |
| 93 | |
| 94 | #nav-music-hoverTips |
| 95 | color: white |
| 96 | background: var(--default-bg-color) |
| 97 | width: 100% |
| 98 | height: 100% |
| 99 | position: absolute |
| 100 | top: 0 |
| 101 | left: 0 |
| 102 | align-items: center |
| 103 | justify-content: center |
| 104 | display: flex |
| 105 | border-radius: 40px |
| 106 | opacity: 0 |
| 107 | font-size: 12px |
| 108 | z-index: 2 |
| 109 | transition: 0.3s |
| 110 | |
| 111 | .aplayer |
| 112 | background: var(--liushen-card-bg) |
| 113 | border-radius: 60px |
| 114 | height: 41px |
| 115 | display: flex |
| 116 | margin: 0 |
| 117 | transition: 0.3s |
| 118 | border: var(--liushen-card-border) |
| 119 | box-shadow: none |
| 120 | |
| 121 | .aplayer-notice, |
| 122 | .aplayer-miniswitcher, |
| 123 | .aplayer-list |
| 124 | display: none |
| 125 | |
| 126 | .aplayer-body |
| 127 | position: relative |
| 128 | display: flex |
| 129 | align-items: center |
| 130 | min-width: 180px |
| 131 | |
| 132 | .aplayer-info |
| 133 | .aplayer-music |
| 134 | margin: 0 |
| 135 | display: flex |
| 136 | align-items: center |
| 137 | padding: 0 12px 0 8px |
| 138 | cursor: pointer |
| 139 | z-index: 1 |
| 140 | height: 100% |
| 141 | |
| 142 | .aplayer-title |
| 143 | cursor: pointer |
| 144 | line-height: 1 |
| 145 | display: inline-block |
| 146 | white-space: nowrap |
| 147 | max-width: 120px |
| 148 | overflow: hidden |
| 149 | text-overflow: ellipsis |
| 150 | transition: 0.3s |
| 151 | user-select: none |
| 152 | |
| 153 | .aplayer-controller |
| 154 | position: absolute |
| 155 | width: 100% |
| 156 | height: 100% |
| 157 | top: 0 |
| 158 | left: 0 |
| 159 | |
| 160 | .aplayer-bar-wrap |
| 161 | margin: 0 |
| 162 | padding: 0 |
| 163 | |
| 164 | .aplayer-bar |
| 165 | height: 100% |
| 166 | background: 0 0 |
| 167 | |
| 168 | .aplayer-loaded |
| 169 | display: none |
| 170 | |
| 171 | .aplayer-played |
| 172 | height: 100% |
| 173 | opacity: 0.1 |
| 174 | background-color: white !important |
| 175 | animation: lightBar 5s ease infinite |
| 176 | animation-play-state: paused |
| 177 | |
| 178 | .aplayer-pic |
| 179 | pointer-events: none |
| 180 | |
| 181 | .aplayer-button |
| 182 | bottom: 50% |
| 183 | right: 50% |
| 184 | transform: translate(50%, 50%) |
| 185 | margin: 0 |
| 186 | transition: 0.3s |
| 187 | pointer-events all |
| 188 | |
| 189 | &:has(.aplayer-button.aplayer-play) |
| 190 | animation-play-state: paused |
| 191 | transform: rotate(0deg) scale(1) !important |
| 192 | |
| 193 | margin-left: 8px |
| 194 | |
| 195 | .aplayer-info .aplayer-controller .aplayer-time, |
| 196 | .aplayer-info .aplayer-music .aplayer-author |
| 197 | display: none |
| 198 | |
| 199 | &.aplayer-withlist .aplayer-info |
| 200 | border: none |
| 201 | |
| 202 | .aplayer-lrc |
| 203 | width: 0 |
| 204 | opacity: 0 |
| 205 | transition: 0.3s |
| 206 | margin-bottom: -26px |
| 207 | |
| 208 | p.aplayer-lrc-current |
| 209 | color: white |
| 210 | border: none |
| 211 | min-height: 20px |
| 212 | filter: none |
| 213 | |
| 214 | &:after, |
| 215 | &:before |
| 216 | display: none |
| 217 | |
| 218 | p |
| 219 | color: #ffffffb3 |
| 220 | filter: blur(.8px) |
| 221 | |
| 222 | @media screen and (min-width: 600px) |
| 223 | #nav-music.stretch .aplayer.aplayer-withlrc .aplayer-lrc |
| 224 | width: 200px |
| 225 | margin-left: 8px |
| 226 | opacity: 1 |
| 227 | |
| 228 | .aplayer-thumb |
| 229 | width: 0 !important |
| 230 | height: 0 !important |
| 231 |
| 1 | const liuMusic = { |
| 2 | musicPlaying: false, |
| 3 | isMusicBind: false, |
| 4 | |
| 5 | musicToggle(isMeting = true) { |
| 6 | if (!this.isMusicBind) { |
| 7 | this.musicBind(); |
| 8 | } |
| 9 | |
| 10 | const $music = document.querySelector("#nav-music"); |
| 11 | const $meting = document.querySelector("meting-js"); |
| 12 | const $console = document.getElementById("consoleMusic"); |
| 13 | |
| 14 | this.musicPlaying = !this.musicPlaying; |
| 15 | $music?.classList.toggle("playing", this.musicPlaying); |
| 16 | $music?.classList.toggle("stretch", this.musicPlaying); |
| 17 | $console?.classList.toggle("on", this.musicPlaying); |
| 18 | |
| 19 | if (isMeting) { |
| 20 | this.musicPlaying ? $meting?.aplayer?.play() : $meting?.aplayer?.pause(); |
| 21 | } |
| 22 | }, |
| 23 | |
| 24 | musicBind() { |
| 25 | const $music = document.querySelector("#nav-music"); |
| 26 | const $name = document.querySelector("#nav-music .aplayer-music"); |
| 27 | const $button = document.querySelector("#nav-music .aplayer-button"); |
| 28 | |
| 29 | $name?.addEventListener("click", () => { |
| 30 | $music?.classList.toggle("stretch"); |
| 31 | }); |
| 32 | |
| 33 | $button?.addEventListener("click", () => { |
| 34 | this.musicToggle(false); |
| 35 | }); |
| 36 | |
| 37 | this.isMusicBind = true; |
| 38 | } |
| 39 | }; |
| 1 | div.needEndHide#nav-music |
| 2 | #nav-music-hoverTips(onclick='liuMusic.musicToggle()')= __('music.hit') |
| 3 | meting-js(id=theme.capsule.id server=theme.capsule.server type=theme.capsule.type mutex="true" preload="none" data-lrctype="0" order="random" volume=theme.capsule.volume api=theme.capsule.meting_api) |
| 4 |