Last active 1754739508

Hexo-Theme-Butterfly实现右下角音乐胶囊

1.readme.md Raw

🎵 音乐胶囊模块使用说明

本仓库提供了一个可集成到 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 或其他已加载的脚本文件中。


🔗 集成流程总结

  1. _config.liushen.yml 添加到 项目根目录 并配置 capsule.enable: true
  2. music.pug 放入 blog/themes/liushen/layout/includes/third-party/
  3. 编辑 layout.pug,在底部添加 include ./third-party/music.pug(带条件判断)。
  4. music-capsule.styl 放入 blog/themes/liushen/source/css/__layout/
  5. music.js 放入 JS 目录并在页面加载时引入,或者可以添加到任意js文件中。
  6. 根据需要修改 CSS 变量配色。
_comfig.liushen.yml Raw
1# 页脚音乐胶囊
2capsule:
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
music-capsule.styl Raw
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
music.js Raw
1const 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};
music.pug Raw
1div.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