├── .gitignore ├── toc ├── readme_doc │ ├── mobile.gif │ └── sidebar.gif ├── README.md └── toc.js ├── clickFireworksEffects ├── readme_doc │ └── PixPin_2024-09-09_13-12-25.png ├── README.md └── clickFireworksEffects.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .history 2 | -------------------------------------------------------------------------------- /toc/readme_doc/mobile.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hslr-s/sun-panel-js-plugins/HEAD/toc/readme_doc/mobile.gif -------------------------------------------------------------------------------- /toc/readme_doc/sidebar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hslr-s/sun-panel-js-plugins/HEAD/toc/readme_doc/sidebar.gif -------------------------------------------------------------------------------- /clickFireworksEffects/readme_doc/PixPin_2024-09-09_13-12-25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hslr-s/sun-panel-js-plugins/HEAD/clickFireworksEffects/readme_doc/PixPin_2024-09-09_13-12-25.png -------------------------------------------------------------------------------- /clickFireworksEffects/README.md: -------------------------------------------------------------------------------- 1 | # Sun-Panel ClickFireworksEffects (点击烟花特效) 2 | [Sun-Panel](https://sun-panel.top) JavaScript plugins. 3 | 4 | ## 插件开发作者 5 | 6 | - 作者:[AlanWang2333](https://github.com/AlanWang2333) 7 | - 来源:[Discussions](https://github.com/hslr-s/sun-panel/discussions/232) 8 | 9 | ## 说明 10 | -- 11 | 12 | ## 效果 13 | 14 | 随意点击触发效果 15 | 16 | ![](./readme_doc/PixPin_2024-09-09_13-12-25.png) 17 | 18 | 19 | ## 安装教程 20 | 21 | 参考 [README.md](../README.md) -------------------------------------------------------------------------------- /toc/README.md: -------------------------------------------------------------------------------- 1 | # Sun-Panel TOC 2 | [Sun-Panel](https://sun-panel.top) JavaScript plugins. 3 | 4 | ## 说明 5 | 提取所有分组作为侧边栏目录,点击可以快速跳转到指定分组。需要在 Sun-Panel `v1.5.2-beta24-08-10` 及以上版本。 6 | 7 | ## 效果 8 | 9 | 按钮触发效果 10 | 11 | ![](./readme_doc/mobile.gif) 12 | 13 | 14 | 侧边栏效果 15 | 16 | ![](./readme_doc/sidebar.gif) 17 | 18 | ## 配置说明 19 | 20 | 部分配置可以在 [toc.js](./toc.js) 配置区域查看到 21 | 22 | ```js 23 | 24 | // =========== Config Start =========== 25 | // ------------------------------------ 26 | // 距离滚动偏移量 27 | const scrollOffset = 80 28 | 29 | // 显示风格( auto:自动(默认) | mobile:左上角显示触发按钮-移动端风格 | sidebar:常态显示侧栏) 30 | const displayStyle = 'auto' 31 | 32 | // 移动端宽度定义 33 | const mobileWidth = 800 34 | 35 | const SunPanelTOCDomIdName = 'sun-panel-toc-dom' 36 | 37 | // 左上角按钮 SVG 图标 38 | const svgTocMobileBtn = '...' 39 | 40 | // ------------------------------------ 41 | // =========== Config End =========== 42 | ``` 43 | 44 | ## 安装教程 45 | 46 | 参考 [README.md](../README.md) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sun-Panel JS-PlugIn 2 | 3 | [Sun-Panel](https://sun-panel.top) JavaScript plugins. 4 | 5 | ## 开发说明 6 | 7 | 插件开发暂时没有提供任何规范以及相关助手函数,可以有很高的自由度去开发。通过插件开发,可以实现很多丰富的功能。如果您愿意分享您开发的插件,可以 [Fork](https://github.com/hslr-s/sun-panel-js-plugins/fork) 本项目,然后创建自己的插件并推送。 8 | 9 | ### 目录说明 10 | 根目录中每个插件都是一个插件,每个插件都有相关的使用介绍和说明。 11 | 12 | ### 参与说明: 13 | - 在本项目根目录创建自己的插件目录,并将主文件设置为与目录同名 `.js` 文件 14 | - 在插件内部建立 `readme.md` 文件,写出插件具体的插件的作者信息、介绍、使用说明。 15 | - 为了安全需要,请尽量不要压缩代码,如果代码量巨大可以压缩插件的主文件并提供完整的未压缩源码。 16 | - 请尽量使用匿名函数调用模式,不要过多的使用全局变量,避免导致不必要的问题。 17 | 18 | ## 安装说明 19 | 20 | ### 1. 独立账号 - 自定义页脚引入 `.js` 文件(推荐) 21 | 22 | 此安装方式需要Sun-Panel `v1.5.2-beta24-08-10` 及以上版本生效,低于此版本请使用全局JS的方法。 23 | 24 | #### 开始 25 | 26 | 1. 找到使用的插件主文件(一般与目录同名的`.js`文件),将它放在 Sun-Panel 的自定义目录中。 27 | 2. 在想要使用的账号中,找到自定义页脚设置,然后在里面加入以下代码, 28 | ```html 29 | 30 | ``` 31 | `toc.js` 改为插件的主文件,刷新后可以查看效果。 32 | 33 | 34 | ### 2. 独立账号 - 自定义页脚直接放入主文件代码 35 | 36 | 此安装方式需要Sun-Panel `v1.5.2-beta24-08-10` 及以上版本生效,低于此版本请使用全局JS的方法。 37 | 38 | #### 开始 39 | 40 | 找到自定义页脚设置,然后在里面加入以下代码 41 | ```html 42 | 45 | ``` 46 | 47 | 刷新后可以查看效果。 48 | 49 | ### 3. 全局生效 针对所有账号生效 50 | 此安装方式需要Sun-Panel `v1.3.0` 及以上版本生效。 51 | 52 | #### 开始 53 | 在全局设置中找到自定义js功能,将主文件的源码,完整的粘贴到里面,保存后刷新页面查看效果 54 | -------------------------------------------------------------------------------- /clickFireworksEffects/clickFireworksEffects.js: -------------------------------------------------------------------------------- 1 | function clickFireworksEffects() { 2 | let balls = []; 3 | let longPressed = false; 4 | let longPress; 5 | let multiplier = 0; 6 | let width, height; 7 | let origin; 8 | let normal; 9 | let ctx; 10 | const colours = ["#F73859", "#14FFEC", "#00E0FF", "#FF99FE", "#FAF15D"]; 11 | const canvas = document.createElement("canvas"); 12 | document.body.appendChild(canvas); 13 | canvas.setAttribute("style", "width: 100%; height: 100%; top: 0; left: 0; z-index: 99999; position: fixed; pointer-events: none;"); 14 | const pointer = document.createElement("span"); 15 | pointer.classList.add("pointer"); 16 | document.body.appendChild(pointer); 17 | 18 | if (canvas.getContext && window.addEventListener) { 19 | ctx = canvas.getContext("2d"); 20 | updateSize(); 21 | window.addEventListener('resize', updateSize, false); 22 | loop(); 23 | window.addEventListener("mousedown", function(e) { 24 | pushBalls(randBetween(10, 20), e.clientX, e.clientY); 25 | document.body.classList.add("is-pressed"); 26 | longPress = setTimeout(function(){ 27 | document.body.classList.add("is-longpress"); 28 | longPressed = true; 29 | }, 500); 30 | }, false); 31 | window.addEventListener("mouseup", function(e) { 32 | clearInterval(longPress); 33 | if (longPressed == true) { 34 | document.body.classList.remove("is-longpress"); 35 | pushBalls(randBetween(50 + Math.ceil(multiplier), 100 + Math.ceil(multiplier)), e.clientX, e.clientY); 36 | longPressed = false; 37 | } 38 | document.body.classList.remove("is-pressed"); 39 | }, false); 40 | window.addEventListener("mousemove", function(e) { 41 | let x = e.clientX; 42 | let y = e.clientY; 43 | pointer.style.top = y + "px"; 44 | pointer.style.left = x + "px"; 45 | }, false); 46 | } else { 47 | console.log("canvas or addEventListener is unsupported!"); 48 | } 49 | 50 | 51 | function updateSize() { 52 | canvas.width = window.innerWidth * 2; 53 | canvas.height = window.innerHeight * 2; 54 | canvas.style.width = window.innerWidth + 'px'; 55 | canvas.style.height = window.innerHeight + 'px'; 56 | ctx.scale(2, 2); 57 | width = (canvas.width = window.innerWidth); 58 | height = (canvas.height = window.innerHeight); 59 | origin = { 60 | x: width / 2, 61 | y: height / 2 62 | }; 63 | normal = { 64 | x: width / 2, 65 | y: height / 2 66 | }; 67 | } 68 | class Ball { 69 | constructor(x = origin.x, y = origin.y) { 70 | this.x = x; 71 | this.y = y; 72 | this.angle = Math.PI * 2 * Math.random(); 73 | if (longPressed == true) { 74 | this.multiplier = randBetween(14 + multiplier, 15 + multiplier); 75 | } else { 76 | this.multiplier = randBetween(6, 12); 77 | } 78 | this.vx = (this.multiplier + Math.random() * 0.5) * Math.cos(this.angle); 79 | this.vy = (this.multiplier + Math.random() * 0.5) * Math.sin(this.angle); 80 | this.r = randBetween(8, 12) + 3 * Math.random(); 81 | this.color = colours[Math.floor(Math.random() * colours.length)]; 82 | } 83 | update() { 84 | this.x += this.vx - normal.x; 85 | this.y += this.vy - normal.y; 86 | normal.x = -2 / window.innerWidth * Math.sin(this.angle); 87 | normal.y = -2 / window.innerHeight * Math.cos(this.angle); 88 | this.r -= 0.3; 89 | this.vx *= 0.9; 90 | this.vy *= 0.9; 91 | } 92 | } 93 | 94 | function pushBalls(count = 1, x = origin.x, y = origin.y) { 95 | for (let i = 0; i < count; i++) { 96 | balls.push(new Ball(x, y)); 97 | } 98 | } 99 | 100 | function randBetween(min, max) { 101 | return Math.floor(Math.random() * max) + min; 102 | } 103 | 104 | function loop() { 105 | ctx.fillStyle = "rgba(255, 255, 255, 0)"; 106 | ctx.clearRect(0, 0, canvas.width, canvas.height); 107 | for (let i = 0; i < balls.length; i++) { 108 | let b = balls[i]; 109 | if (b.r < 0) continue; 110 | ctx.fillStyle = b.color; 111 | ctx.beginPath(); 112 | ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2, false); 113 | ctx.fill(); 114 | b.update(); 115 | } 116 | if (longPressed == true) { 117 | multiplier += 0.2; 118 | } else if (!longPressed && multiplier >= 0) { 119 | multiplier -= 0.4; 120 | } 121 | removeBall(); 122 | requestAnimationFrame(loop); 123 | } 124 | 125 | function removeBall() { 126 | for (let i = 0; i < balls.length; i++) { 127 | let b = balls[i]; 128 | if (b.x + b.r < 0 || b.x - b.r > width || b.y + b.r < 0 || b.y - b.r > height || b.r < 0) { 129 | balls.splice(i, 1); 130 | } 131 | } 132 | } 133 | } 134 | clickFireworksEffects();//调用特效函数 135 | -------------------------------------------------------------------------------- /toc/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 = '' 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: 46px; 58 | height: 46px; 59 | background-color: #2a2a2a6b; 60 | color: white; 61 | border-radius: 0.5rem; 62 | display: flex; 63 | justify-content: center; 64 | align-items: center; 65 | cursor: pointer; 66 | } 67 | 68 | ${SunPanelTOCDomStyleId} .hidden { 69 | display: none !important; 70 | } 71 | 72 | ${SunPanelTOCDomStyleId} #toc-sidebar { 73 | width: 40px; 74 | padding: 10px; 75 | position: fixed; 76 | top: 0; 77 | left: 0; 78 | height: 100%; 79 | overflow: hidden; 80 | display: flex; 81 | flex-direction: column; 82 | justify-content: center; 83 | transition: width 0.3s ease, background-color 0.3s ease; 84 | border-top-right-radius: 20px; 85 | border-bottom-right-radius: 20px; 86 | background-color: none; 87 | } 88 | 89 | ${SunPanelTOCDomStyleId} .toc-mobile-btn-svg-container{ 90 | width:21px; 91 | height:21px; 92 | } 93 | 94 | ${SunPanelTOCDomStyleId} .toc-sidebar-expansion { 95 | width: 200px !important; 96 | display: flex; 97 | background-color: rgb(42 42 42 / 90%); 98 | box-shadow: 2px 0 5px rgba(0, 0, 0, 0.2); 99 | } 100 | 101 | ${SunPanelTOCDomStyleId} #toc-sidebar .toc-sidebar-box { 102 | width: 500px; 103 | } 104 | 105 | ${SunPanelTOCDomStyleId} .title-bar-box { 106 | display: flex; 107 | align-items: center; 108 | position: relative; 109 | cursor: pointer; 110 | } 111 | 112 | ${SunPanelTOCDomStyleId} .title-bar-slip { 113 | width: 20px; 114 | height: 6px; 115 | background-color: white; 116 | border-radius: 4px; 117 | margin: 15px 0; 118 | transition: height 0.3s ease, width 0.3s ease; 119 | box-shadow: 2px 0 5px rgba(0, 0, 0, 0.5); 120 | } 121 | 122 | ${SunPanelTOCDomStyleId} .title-bar-title { 123 | opacity: 0; 124 | white-space: nowrap; 125 | transition: opacity 0.3s ease, transform 0.3s ease, margin-left 0.3s ease; 126 | font-size: 14px; 127 | color: white; 128 | } 129 | 130 | ${SunPanelTOCDomStyleId} .toc-sidebar-expansion .title-bar-title { 131 | opacity: 1; 132 | margin-left: 10px; 133 | } 134 | 135 | ${SunPanelTOCDomStyleId} .toc-sidebar-expansion .title-bar-slip { 136 | box-shadow: none; 137 | } 138 | 139 | ${SunPanelTOCDomStyleId} .toc-sidebar-expansion .title-bar-box:hover .title-bar-slip { 140 | width: 40px; 141 | } 142 | 143 | ${SunPanelTOCDomStyleId} .toc-sidebar-expansion .title-bar-box:hover .title-bar-title { 144 | font-size: 20px; 145 | } 146 | 147 | ` 148 | // 添加样式到文档头部 149 | SunPanelTOCDom.appendChild(style) 150 | 151 | // ========= Add style end ========= 152 | 153 | // 添加移动端菜单按钮 154 | const tocMobileBtn = document.createElement('div') 155 | tocMobileBtn.id = 'toc-mobile-btn' 156 | tocMobileBtn.classList.add('backdrop-blur-[2px]') 157 | SunPanelTOCDom.appendChild(tocMobileBtn) 158 | 159 | const tocMobileBtnSvgcContainer = document.createElement('div') 160 | tocMobileBtnSvgcContainer.innerHTML = svgTocMobileBtn 161 | tocMobileBtnSvgcContainer.classList.add('toc-mobile-btn-svg-container') 162 | tocMobileBtn.appendChild(tocMobileBtnSvgcContainer) 163 | 164 | // 创建侧边栏容器 165 | const sidebar = document.createElement('div') 166 | sidebar.id = 'toc-sidebar' 167 | 168 | const sidebarBox = document.createElement('div') 169 | sidebarBox.className = 'toc-sidebar-box' 170 | 171 | // 查询出所有类名包含 item-group-index- 的元素 172 | const items = document.querySelectorAll('[class*="item-group-index-"]') 173 | 174 | // 遍历并打印每个元素的完整类名 175 | items.forEach((item) => { 176 | item.classList.forEach((className) => { 177 | if (className.startsWith('item-group-index-')) { 178 | const titleBarBox = document.createElement('div') 179 | titleBarBox.className = 'title-bar-box' 180 | // titleBarBox.href = `#${item.id}` 181 | titleBarBox.dataset.groupClassName = className 182 | 183 | // 目录条 184 | const titleBarSlip = document.createElement('div') 185 | titleBarSlip.className = 'title-bar-slip' 186 | 187 | // 创建一个链接 188 | const titleBarTitle = document.createElement('div') 189 | titleBarTitle.className = 'title-bar-title' 190 | 191 | // 获取子元素中 class="group-title" 的内容 192 | const titleElement = item.querySelector('.group-title') 193 | const titleText = titleElement ? titleElement.textContent : item.id 194 | titleBarTitle.textContent = titleText 195 | 196 | titleBarBox.appendChild(titleBarSlip) 197 | titleBarBox.appendChild(titleBarTitle) 198 | 199 | sidebarBox.appendChild(titleBarBox) 200 | } 201 | }) 202 | }) 203 | 204 | sidebar.appendChild(sidebarBox) 205 | 206 | // 将侧边栏添加到页面中 207 | SunPanelTOCDom.appendChild(sidebar) 208 | 209 | function mobileHideSidebar() { 210 | sidebar.classList.remove('toc-sidebar-expansion') 211 | sidebar.classList.add('hidden') 212 | } 213 | 214 | function hideSidebar() { 215 | sidebar.classList.remove('toc-sidebar-expansion') 216 | } 217 | 218 | function showSidebar() { 219 | sidebar.classList.add('toc-sidebar-expansion') 220 | sidebar.classList.remove('hidden') 221 | } 222 | 223 | // ---------------- 224 | // 监听宽度变化开始 225 | // ---------------- 226 | function debounce(func, wait) { 227 | let timeout 228 | return function (...args) { 229 | clearTimeout(timeout) 230 | timeout = setTimeout(() => { 231 | func.apply(this, args) 232 | }, wait) 233 | } 234 | } 235 | 236 | function handleResize() { 237 | if (isMobile()) { 238 | tocMobileBtn.classList.remove('hidden') 239 | sidebar.classList.add('hidden') 240 | } 241 | else { 242 | tocMobileBtn.classList.add('hidden') 243 | sidebar.classList.remove('hidden') 244 | } 245 | } 246 | 247 | // 使用防抖函数包装你的处理函数 248 | const debouncedHandleResize = debounce(handleResize, 200) 249 | 250 | // 添加事件监听器 251 | window.addEventListener('resize', debouncedHandleResize) 252 | 253 | // 首次触发 254 | handleResize() 255 | 256 | // ---------------- 257 | // 监听宽度变化结束 258 | // ---------------- 259 | 260 | // 监听移动端按钮点击 261 | tocMobileBtn.addEventListener('click', () => { 262 | if (sidebar.classList.contains('toc-sidebar-expansion')) { 263 | // 隐藏 264 | mobileHideSidebar() 265 | } 266 | else { 267 | // 显示 268 | showSidebar() 269 | } 270 | }) 271 | 272 | // 监听TOC栏失去hover 273 | sidebar.addEventListener('mouseleave', () => { 274 | if (isMobile()) { 275 | // 隐藏 276 | mobileHideSidebar() 277 | } 278 | else { 279 | hideSidebar() 280 | } 281 | }) 282 | 283 | // 监听TOC栏获得hover 284 | sidebar.addEventListener('mouseenter', () => { 285 | showSidebar() 286 | }) 287 | 288 | // 监听TOC点击事件 289 | document.querySelectorAll('.title-bar-box').forEach((box) => { 290 | box.addEventListener('click', function (event) { 291 | // 检查触发事件的元素是否有 'data-groupClassName' 属性 292 | if (this.dataset.groupClassName) { 293 | // 获取 'data-groupClass' 属性的值 294 | const groupClassName = this.dataset.groupClassName 295 | // 使用属性值作为选择器查询对应的元素 296 | const targetElement = document.querySelector(`.${groupClassName}`) 297 | if (targetElement) { 298 | // 获取目标元素的 'top' 坐标 299 | const targetTop = targetElement.offsetTop 300 | const scrollContainerElement = document.querySelector(scrollContainerElementClassName) 301 | if (scrollContainerElement) { 302 | scrollContainerElement.scrollTo({ 303 | top: targetTop - scrollOffset, 304 | behavior: 'smooth', // 平滑滚动 305 | }) 306 | } 307 | } 308 | } 309 | }) 310 | }) 311 | } 312 | 313 | // 判断是否已经存在分组,不存在将定时监听 314 | const items = document.querySelectorAll('[class*="item-group-index-"]') 315 | if (items.length > 0) { 316 | createDom() 317 | return 318 | } 319 | 320 | const interval = setInterval(() => { 321 | const items = document.querySelectorAll('[class*="item-group-index-"]') 322 | if (items.length > 0) { 323 | createDom() 324 | clearInterval(interval) 325 | } 326 | }, 1000) 327 | })() 328 | --------------------------------------------------------------------------------