Последняя активность 3 months ago

基于 Sun-Panel 原作者"红烧猎人"提供的标题插件进行优化和美化,使其与天气插件的显示效果完全统一,实现完美的一体化体验。

toc.js Исходник
1(function () {
2 // =========== Config Start ===========
3 // ------------------------------------
4 // 距离滚动偏移量
5 const scrollOffset = 80
6
7 // 显示风格( auto:自动(默认) | mobile:左上角显示触发按钮-移动端风格 | sidebar:常态显示侧栏)
8 const displayStyle = 'auto'
9
10 // 移动端宽度定义
11 const mobileWidth = 800
12
13 const SunPanelTOCDomIdName = 'sun-panel-toc-dom'
14
15 // 左上角按钮 SVG 图标
16 const svgTocMobileBtn = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M17.5 4.5c-1.95 0-4.05.4-5.5 1.5c-1.45-1.1-3.55-1.5-5.5-1.5c-1.45 0-2.99.22-4.28.79C1.49 5.62 1 6.33 1 7.14v11.28c0 1.3 1.22 2.26 2.48 1.94c.98-.25 2.02-.36 3.02-.36c1.56 0 3.22.26 4.56.92c.6.3 1.28.3 1.87 0c1.34-.67 3-.92 4.56-.92c1 0 2.04.11 3.02.36c1.26.33 2.48-.63 2.48-1.94V7.14c0-.81-.49-1.52-1.22-1.85c-1.28-.57-2.82-.79-4.27-.79M21 17.23c0 .63-.58 1.09-1.2.98c-.75-.14-1.53-.2-2.3-.2c-1.7 0-4.15.65-5.5 1.5V8c1.35-.85 3.8-1.5 5.5-1.5c.92 0 1.83.09 2.7.28c.46.1.8.51.8.98z"/><path fill="currentColor" d="M13.98 11.01c-.32 0-.61-.2-.71-.52c-.13-.39.09-.82.48-.94c1.54-.5 3.53-.66 5.36-.45c.41.05.71.42.66.83s-.42.71-.83.66c-1.62-.19-3.39-.04-4.73.39c-.08.01-.16.03-.23.03m0 2.66c-.32 0-.61-.2-.71-.52c-.13-.39.09-.82.48-.94c1.53-.5 3.53-.66 5.36-.45c.41.05.71.42.66.83s-.42.71-.83.66c-1.62-.19-3.39-.04-4.73.39a1 1 0 0 1-.23.03m0 2.66c-.32 0-.61-.2-.71-.52c-.13-.39.09-.82.48-.94c1.53-.5 3.53-.66 5.36-.45c.41.05.71.42.66.83s-.42.7-.83.66c-1.62-.19-3.39-.04-4.73.39a1 1 0 0 1-.23.03"/></svg>'
17
18 // ------------------------------------
19 // =========== Config End ===========
20
21 // 滚动容器的类名
22 const scrollContainerElementClassName = '.scroll-container'
23
24 // 一些函数
25 const isMobile = () => {
26 if (displayStyle === 'mobile') {
27 return true
28 }
29 else if (displayStyle === 'pc') {
30 return false
31 }
32 const width = window.innerWidth
33 return width < mobileWidth
34 }
35
36 function createDom() {
37 // 检测是否已经存在TOC DOM,存在则删除
38 (function () {
39 const element = document.getElementById(SunPanelTOCDomIdName)
40 if (element) {
41 element.remove()
42 }
43 })()
44
45 const SunPanelTOCDom = document.createElement('div')
46 SunPanelTOCDom.id = SunPanelTOCDomIdName
47 document.body.appendChild(SunPanelTOCDom)
48
49 // ========= Add style start =========
50 const style = document.createElement('style')
51 const SunPanelTOCDomStyleId = `#${SunPanelTOCDomIdName}`
52 style.textContent = `
53 ${SunPanelTOCDomStyleId} #toc-mobile-btn {
54 top: 20px !important;
55 left: 20px !important;
56 position: fixed;
57 width: 44px;
58 height: 44px;
59 background: rgba(255, 255, 255, 0.15);
60 backdrop-filter: blur(10px);
61 border-radius: 12px;
62 display: flex;
63 justify-content: center;
64 align-items: center;
65 cursor: pointer;
66 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
67 border: 1px solid rgba(255, 255, 255, 0.2);
68 z-index: 9998;
69 transition: all 0.3s ease;
70 }
71
72 ${SunPanelTOCDomStyleId} #toc-mobile-btn:hover {
73 transform: scale(1.05);
74 background: rgba(255, 255, 255, 0.25);
75 }
76
77 ${SunPanelTOCDomStyleId} .hidden {
78 display: none !important;
79 }
80
81 ${SunPanelTOCDomStyleId} #toc-sidebar {
82 width: 20px;
83 padding: 10px;
84 position: fixed;
85 top: 0;
86 left: 0;
87 height: 100%;
88 overflow: hidden;
89 display: flex;
90 flex-direction: column;
91 justify-content: center;
92 transition: width 0.3s ease, background-color 0.3s ease;
93 border-top-right-radius: 16px;
94 border-bottom-right-radius: 16px;
95 background: rgba(255, 255, 255, 0.15);
96 backdrop-filter: blur(15px);
97 border: 1px solid rgba(255, 255, 255, 0.2);
98 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
99 z-index: 9999;
100 }
101
102 ${SunPanelTOCDomStyleId} .toc-mobile-btn-svg-container{
103 width:21px;
104 height:21px;
105 color: rgba(255, 255, 255, 0.9);
106 }
107
108 ${SunPanelTOCDomStyleId} .toc-sidebar-expansion {
109 width: 180px !important;
110 display: flex;
111 background: rgba(255, 255, 255, 0.15);
112 backdrop-filter: blur(15px);
113 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
114 }
115
116 ${SunPanelTOCDomStyleId} #toc-sidebar .toc-sidebar-box {
117 width: 100%;
118 padding: 20px 10px;
119 }
120
121 ${SunPanelTOCDomStyleId} .title-bar-box {
122 display: flex;
123 align-items: center;
124 position: relative;
125 cursor: pointer;
126 margin-bottom: 15px;
127 padding: 8px 12px;
128 border-radius: 8px;
129 transition: all 0.3s ease;
130 }
131
132 ${SunPanelTOCDomStyleId} .title-bar-box:hover {
133 background: rgba(255, 255, 255, 0.1);
134 }
135
136 ${SunPanelTOCDomStyleId} .title-bar-slip {
137 width: 20px;
138 height: 6px;
139 background-color: rgba(255, 255, 255, 0.8);
140 border-radius: 4px;
141 transition: height 0.3s ease, width 0.3s ease;
142 }
143
144 ${SunPanelTOCDomStyleId} .title-bar-title {
145 opacity: 0;
146 white-space: nowrap;
147 transition: opacity 0.3s ease, transform 0.3s ease, margin-left 0.3s ease;
148 font-size: 14px;
149 color: white;
150 font-weight: 500;
151 }
152
153 ${SunPanelTOCDomStyleId} .toc-sidebar-expansion .title-bar-title {
154 opacity: 1;
155 margin-left: 12px;
156 }
157
158 ${SunPanelTOCDomStyleId} .toc-sidebar-expansion .title-bar-box:hover .title-bar-slip {
159 width: 30px;
160 background-color: rgba(255, 255, 255, 1);
161 }
162
163 ${SunPanelTOCDomStyleId} .toc-sidebar-expansion .title-bar-box:hover .title-bar-title {
164 transform: translateX(5px);
165 color: rgba(255, 255, 255, 1);
166 }
167
168 /* 移动端侧边栏样式 */
169 ${SunPanelTOCDomStyleId} .toc-mobile-sidebar {
170 width: 180px !important;
171 height: 100%;
172 border-radius: 0 16px 16px 0;
173 background: rgba(255, 255, 255, 0.15);
174 backdrop-filter: blur(15px);
175 z-index: 10000;
176 }
177
178 ${SunPanelTOCDomStyleId} .toc-close-btn {
179 position: absolute;
180 top: 20px;
181 right: 20px;
182 width: 44px;
183 height: 44px;
184 background: rgba(255, 255, 255, 0.15);
185 backdrop-filter: blur(10px);
186 border-radius: 12px;
187 display: flex;
188 justify-content: center;
189 align-items: center;
190 cursor: pointer;
191 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
192 border: 1px solid rgba(255, 255, 255, 0.2);
193 z-index: 10001;
194 transition: all 0.3s ease;
195 }
196
197 ${SunPanelTOCDomStyleId} .toc-close-btn:hover {
198 transform: scale(1.05);
199 background: rgba(255, 255, 255, 0.25);
200 }
201
202 ${SunPanelTOCDomStyleId} .toc-close-btn svg {
203 width: 20px;
204 height: 20px;
205 color: rgba(255, 255, 255, 0.9);
206 }
207
208 `
209 // 添加样式到文档头部
210 SunPanelTOCDom.appendChild(style)
211
212 // ========= Add style end =========
213
214 // 添加移动端菜单按钮
215 const tocMobileBtn = document.createElement('div')
216 tocMobileBtn.id = 'toc-mobile-btn'
217 SunPanelTOCDom.appendChild(tocMobileBtn)
218
219 const tocMobileBtnSvgcContainer = document.createElement('div')
220 tocMobileBtnSvgcContainer.innerHTML = svgTocMobileBtn
221 tocMobileBtnSvgcContainer.classList.add('toc-mobile-btn-svg-container')
222 tocMobileBtn.appendChild(tocMobileBtnSvgcContainer)
223
224 // 创建侧边栏容器
225 const sidebar = document.createElement('div')
226 sidebar.id = 'toc-sidebar'
227
228 const sidebarBox = document.createElement('div')
229 sidebarBox.className = 'toc-sidebar-box'
230
231 // 查询出所有类名包含 item-group-index- 的元素
232 const items = document.querySelectorAll('[class*="item-group-index-"]')
233
234 // 遍历并打印每个元素的完整类名
235 items.forEach((item) => {
236 item.classList.forEach((className) => {
237 if (className.startsWith('item-group-index-')) {
238 const titleBarBox = document.createElement('div')
239 titleBarBox.className = 'title-bar-box'
240 titleBarBox.dataset.groupClassName = className
241
242 // 目录条
243 const titleBarSlip = document.createElement('div')
244 titleBarSlip.className = 'title-bar-slip'
245
246 // 创建一个链接
247 const titleBarTitle = document.createElement('div')
248 titleBarTitle.className = 'title-bar-title'
249
250 // 获取子元素中 class="group-title" 的内容
251 const titleElement = item.querySelector('.group-title')
252 const titleText = titleElement ? titleElement.textContent : item.id
253 titleBarTitle.textContent = titleText
254
255 titleBarBox.appendChild(titleBarSlip)
256 titleBarBox.appendChild(titleBarTitle)
257
258 sidebarBox.appendChild(titleBarBox)
259 }
260 })
261 })
262
263 sidebar.appendChild(sidebarBox)
264
265 // 添加关闭按钮(移动端使用)
266 const closeBtn = document.createElement('div')
267 closeBtn.className = 'toc-close-btn hidden'
268 closeBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>'
269 closeBtn.addEventListener('click', (e) => {
270 e.stopPropagation()
271 mobileHideSidebar()
272 })
273 SunPanelTOCDom.appendChild(closeBtn)
274
275 // 将侧边栏添加到页面中
276 SunPanelTOCDom.appendChild(sidebar)
277
278 function mobileHideSidebar() {
279 sidebar.classList.remove('toc-sidebar-expansion', 'toc-mobile-sidebar')
280 sidebar.classList.add('hidden')
281 closeBtn.classList.add('hidden')
282 }
283
284 function hideSidebar() {
285 sidebar.classList.remove('toc-sidebar-expansion')
286 }
287
288 function showSidebar() {
289 sidebar.classList.add('toc-sidebar-expansion')
290 sidebar.classList.remove('hidden')
291
292 // 移动端显示关闭按钮
293 if (isMobile()) {
294 sidebar.classList.add('toc-mobile-sidebar')
295 closeBtn.classList.remove('hidden')
296 } else {
297 sidebar.classList.remove('toc-mobile-sidebar')
298 closeBtn.classList.add('hidden')
299 }
300 }
301
302 // ----------------
303 // 监听宽度变化开始
304 // ----------------
305 function debounce(func, wait) {
306 let timeout
307 return function (...args) {
308 clearTimeout(timeout)
309 timeout = setTimeout(() => {
310 func.apply(this, args)
311 }, wait)
312 }
313 }
314
315 function handleResize() {
316 if (isMobile()) {
317 tocMobileBtn.classList.remove('hidden')
318 sidebar.classList.add('hidden')
319 closeBtn.classList.add('hidden')
320 }
321 else {
322 tocMobileBtn.classList.add('hidden')
323 sidebar.classList.remove('hidden')
324 closeBtn.classList.add('hidden')
325 }
326 }
327
328 // 使用防抖函数包装你的处理函数
329 const debouncedHandleResize = debounce(handleResize, 200)
330
331 // 添加事件监听器
332 window.addEventListener('resize', debouncedHandleResize)
333
334 // 首次触发
335 handleResize()
336
337 // ----------------
338 // 监听宽度变化结束
339 // ----------------
340
341 // 监听移动端按钮点击
342 tocMobileBtn.addEventListener('click', () => {
343 if (sidebar.classList.contains('toc-sidebar-expansion')) {
344 // 隐藏
345 mobileHideSidebar()
346 }
347 else {
348 // 显示
349 showSidebar()
350 }
351 })
352
353 // 监听TOC栏失去hover
354 sidebar.addEventListener('mouseleave', () => {
355 if (!isMobile()) {
356 hideSidebar()
357 }
358 })
359
360 // 监听TOC栏获得hover
361 sidebar.addEventListener('mouseenter', () => {
362 if (!isMobile()) {
363 showSidebar()
364 }
365 })
366
367 // 监听TOC点击事件
368 document.querySelectorAll('.title-bar-box').forEach((box) => {
369 box.addEventListener('click', function (event) {
370 // 检查触发事件的元素是否有 'data-groupClassName' 属性
371 if (this.dataset.groupClassName) {
372 // 获取 'data-groupClass' 属性的值
373 const groupClassName = this.dataset.groupClassName
374 // 使用属性值作为选择器查询对应的元素
375 const targetElement = document.querySelector(`.${groupClassName}`)
376 if (targetElement) {
377 // 获取目标元素的 'top' 坐标
378 const targetTop = targetElement.offsetTop
379 const scrollContainerElement = document.querySelector(scrollContainerElementClassName)
380 if (scrollContainerElement) {
381 scrollContainerElement.scrollTo({
382 top: targetTop - scrollOffset,
383 behavior: 'smooth', // 平滑滚动
384 })
385 }
386 }
387 }
388
389 // 移动端点击后自动关闭侧边栏
390 if (isMobile()) {
391 mobileHideSidebar()
392 }
393 })
394 })
395 }
396
397 // 判断是否已经存在分组,不存在将定时监听
398 const items = document.querySelectorAll('[class*="item-group-index-"]')
399 if (items.length > 0) {
400 createDom()
401 return
402 }
403
404 const interval = setInterval(() => {
405 const items = document.querySelectorAll('[class*="item-group-index-"]')
406 if (items.length > 0) {
407 createDom()
408 clearInterval(interval)
409 }
410 }, 1000)
411})()
使用方法.md Исходник

直接将代码添加到 Sun-Panel 项目中即可,插件会自动: (具体添加方法请参考原作者“红烧猎人”的“sun-panel-js-plugins”项目)

  • 识别页面中的分组标题
  • 生成对应的导航菜单
  • 适配PC和移动端显示