🎄前言
近来没事儿就折腾折腾Sun-Panel,发现大家对这个美化还是挺有兴趣的,出一个汇总教程,有兴趣的朋友们可以参考下,有好玩的我还会更新帖子。
部分原版方案来自网络,感谢原创作者:Sun-Panel YM-NAV 自定义css js 源码思路、IT 人必备工具箱、时也命也
🎁前置条件
🎫本文所需附件:飞牛私有云分享:https://s.fnnas.net/s/181f744e69ba4686a2,密码:madrays
,打开链接可下载文件
🎆为了支持作者@红烧猎人,本教程大多数需开通订阅后通过方能实现:
🎰多JS、CSS调用脚本
// 加载脚本函数
function loadScript(url) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.type = 'text/javascript';
script.onload = () => resolve();
script.onerror = () => reject(new Error(`Failed to load script: ${url}`));
document.head.appendChild(script);
});
}
// 加载样式表函数
function loadStyle(url) {
return new Promise((resolve, reject) => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
link.onload = () => resolve();
link.onerror = () => reject(new Error(`Failed to load style: ${url}`));
document.head.appendChild(link);
});
}
// 加载多个脚本
async function loadScripts(scripts) {
for (const script of scripts) {
try {
await loadScript(script);
} catch (error) {
console.error(error);
}
}
}
// 加载多个样式表
async function loadStyles(styles) {
const stylePromises = styles.map(style => loadStyle(style));
try {
await Promise.all(stylePromises);
} catch (error) {
console.error(error);
}
}
// 调用函数加载多个样式表
const scriptsToLoad = [
'/custom/fishbackground.js',
'/custom/ai.js',
'/custom/toc.js',
'/custom/mouse.js'
// '/custom/待添加.js', // 如果需要,可以解除注释
];
// 调用函数加载多个脚本
const stylesToLoad = [
'/custom/bk.css',
'/custom/action.css',
'/custom/logo.css',
'/custom/xiantiao.css',
'/custom/mouse.css',
'/custom/loading.css'
// '/custom/待添加.css', // 如果需要,可以解除注释
];
// 调用加载函数
loadScripts(scriptsToLoad);
loadStyles(stylesToLoad);
♦️上述代码为附件all.js
文件,记事本或VSCode
等软件打开后复制到上述JS
脚本输入框即可,需要什么美化,就自己修改下调用路径,这个应该看得懂吧。
♥️美化案例
✨1.渐变背景
body {
/* 100%窗口高度 */
height: 100vh;
/* 更深的色调和不同的渐变方向 */
background: linear-gradient(45deg, #2C3E50, #2980B9, #8E44AD, #E74C3C);
/* 指定背景图像的大小 */
background-size: 400% 400%;
/* 执行动画:动画名 时长 缓动函数 无限次播放 */
animation: action 30s ease-in-out infinite;
}
/* 定义动画 */
@keyframes action {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
上述代码为附件中的bk.css
文件,存放至sunpanel
安装目录的./conf/custom/
文件夹下,在前置条件给出的js
代码中// 调用函数加载多个样式表
下方增加一行'/custom/bk.css',
即可调用。需注意渐变背景需删除原有背景才可以实现:壁纸-编辑图像链接-清空输入框-保存
✨2.鼠标悬停动画
/*鼠标悬停动画 */
/* 当鼠标悬停在图标信息框上时触发动画 */
/* 详细图标摇晃动画 */
.icon-info-box .rounded-2xl:hover {
background: #b3f1e2e6 !important;
/* 背景颜色变成深灰色 */
-webkit-animation: info-shake-bounce .5s alternate !important;
-moz-animation: info-shake-bounce .5s alternate !important;
-o-animation: info-shake-bounce .5s alternate !important;
animation: info-shake-bounce .5s alternate !important;
}
/* 小图标摇晃动画 */
.icon-small-box .rounded-2xl:hover {
background: #b3f1e2e6 !important;
/* 背景颜色变成深灰色 */
-webkit-animation: small-shake-bounce .5s alternate !important;
-moz-animation: small-shake-bounce .5s alternate !important;
-o-animation: small-shake-bounce .5s alternate !important;
animation: small-shake-bounce .5s alternate !important;
}
/* 定义摇详细图标晃弹跳动画的关键帧 */
@keyframes info-shake-bounce {
0%,
100% {
transform: rotate(0);
}
25% {
transform: rotate(10deg);
}
50% {
transform: rotate(-10deg);
}
75% {
transform: rotate(2.5deg);
}
85% {
transform: rotate(-2.5deg);
}
}
/* 定义摇小图标晃弹跳动画的关键帧 */
@keyframes small-shake-bounce {
0%,
100% {
transform: rotate(0);
}
25% {
transform: rotate(15deg);
}
50% {
transform: rotate(-15deg);
}
75% {
transform: rotate(5deg);
}
85% {
transform: rotate(5deg);
}
}
上述代码为附件中的action.css
文件,存放至sunpanel
安装目录的./conf/custom/
文件夹下,在前置条件给出的js
代码中// 调用函数加载多个样式表
下方增加一行'/custom/action.css',
即可调用。
✨3.网页播放器
网页播放器尝试了各种开源的,还是只有明月浩空网的能用,点这里注册账号,按教程自定义下,免费的也够用了,需要注意我们设置播放器放置在右侧,为sunpanel
左侧边栏留出位置避免冲突,然后要开启加载jQuery
。
试了半天,免插件代码用不了哈!
只能用JS
代码了,替换下面代码中的yourid
为你的播放器ID即可。
var script = document.createElement("script");
script.setAttribute("type","text/javascript");
script.setAttribute("id","myhk");
script.setAttribute("src","https://myhkw.cn/api/player/yourid");
script.setAttribute("key","yourid");
document.documentElement.appendChild(script);
上述代码为附件中的myhk.js
文件,存放至sunpanel
安装目录的./conf/custom/
文件夹下,在前置条件给出的js
代码中// 调用函数加载多个脚本
下方增加一行'/custom/myhk.js',
即可调用。
✨4.白化版侧边导航栏
(function () {
// =========== Config Start ===========
// ------------------------------------
// 距离滚动偏移量
const scrollOffset = 80
// 显示风格( auto:自动(默认) | mobile:左上角显示触发按钮-移动端风格 | sidebar:常态显示侧栏)
const displayStyle = 'auto'
// 移动端宽度定义
const mobileWidth = 800
const SunPanelTOCDomIdName = 'sun-panel-toc-dom'
// 左上角按钮 SVG 图标
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>'
// ------------------------------------
// =========== Config End ===========
// 滚动容器的类名
const scrollContainerElementClassName = '.scroll-container'
// 一些函数
const isMobile = () => {
if (displayStyle === 'mobile') {
return true
}
else if (displayStyle === 'pc') {
return false
}
const width = window.innerWidth
return width < mobileWidth
}
function createDom() {
// 检测是否已经存在TOC DOM,存在则删除
(function () {
const element = document.getElementById(SunPanelTOCDomIdName)
if (element) {
element.remove()
}
})()
const SunPanelTOCDom = document.createElement('div')
SunPanelTOCDom.id = SunPanelTOCDomIdName
document.body.appendChild(SunPanelTOCDom)
// ========= Add style start =========
const style = document.createElement('style')
const SunPanelTOCDomStyleId = `#${SunPanelTOCDomIdName}`
style.textContent = `
${SunPanelTOCDomStyleId} #toc-mobile-btn {
top: 20px !important;
left: 20px !important;
position: fixed;
width: 46px;
height: 46px;
background-color:#2a2a2a6b;
color: white;
border-radius: 0.5rem;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
${SunPanelTOCDomStyleId} .hidden {
display: none !important;
}
${SunPanelTOCDomStyleId} #toc-sidebar {
width: 40px;
padding: 10px;
position: fixed;
top: 0;
left: 0;
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: center;
transition: width 0.3s ease, background-color 0.3s ease;
border-top-right-radius: 20px;
border-bottom-right-radius: 20px;
background-color: none;
}
${SunPanelTOCDomStyleId} .toc-mobile-btn-svg-container{
width:21px;
height:21px;
}
${SunPanelTOCDomStyleId} .toc-sidebar-expansion {
width: 200px !important;
display: flex;
background-color: rgb(255 255 255 / 20%);
box-shadow: 1px 0 5px rgba(247, 238, 238, 0.66);
}
${SunPanelTOCDomStyleId} #toc-sidebar .toc-sidebar-box {
width: 500px;
}
${SunPanelTOCDomStyleId} .title-bar-box {
display: flex;
align-items: center;
position: relative;
cursor: pointer;
}
${SunPanelTOCDomStyleId} .title-bar-slip {
width: 20px;
height: 6px;
background-color: white;
border-radius: 5px;
margin: 15px 0;
transition: height 0.3s ease, width 0.3s ease;
box-shadow: 1px 0 5px rgba(248, 216, 216, 0.88);
}
${SunPanelTOCDomStyleId} .title-bar-title {
opacity: 0;
white-space: nowrap;
transition: opacity 0.3s ease, transform 0.3s ease, margin-left 0.3s ease;
font-size: 15px;
color:rgba(255, 255, 255, 1);
}
${SunPanelTOCDomStyleId} .toc-sidebar-expansion .title-bar-title {
opacity: 1;
margin-left: 10px;
}
${SunPanelTOCDomStyleId} .toc-sidebar-expansion .title-bar-slip {
box-shadow: none;
}
${SunPanelTOCDomStyleId} .toc-sidebar-expansion .title-bar-box:hover .title-bar-slip {
width: 40px;
}
${SunPanelTOCDomStyleId} .toc-sidebar-expansion .title-bar-box:hover .title-bar-title {
font-size: 20px;
}
`
// 添加样式到文档头部
SunPanelTOCDom.appendChild(style)
// ========= Add style end =========
// 添加移动端菜单按钮
const tocMobileBtn = document.createElement('div')
tocMobileBtn.id = 'toc-mobile-btn'
tocMobileBtn.classList.add('backdrop-blur-[2px]')
SunPanelTOCDom.appendChild(tocMobileBtn)
const tocMobileBtnSvgcContainer = document.createElement('div')
tocMobileBtnSvgcContainer.innerHTML = svgTocMobileBtn
tocMobileBtnSvgcContainer.classList.add('toc-mobile-btn-svg-container')
tocMobileBtn.appendChild(tocMobileBtnSvgcContainer)
// 创建侧边栏容器
const sidebar = document.createElement('div')
sidebar.id = 'toc-sidebar'
const sidebarBox = document.createElement('div')
sidebarBox.className = 'toc-sidebar-box'
// 查询出所有类名包含 item-group-index- 的元素
const items = document.querySelectorAll('[class*="item-group-index-"]')
// 遍历并打印每个元素的完整类名
items.forEach((item) => {
item.classList.forEach((className) => {
if (className.startsWith('item-group-index-')) {
const titleBarBox = document.createElement('div')
titleBarBox.className = 'title-bar-box'
// titleBarBox.href = `#${item.id}`
titleBarBox.dataset.groupClassName = className
// 目录条
const titleBarSlip = document.createElement('div')
titleBarSlip.className = 'title-bar-slip'
// 创建一个链接
const titleBarTitle = document.createElement('div')
titleBarTitle.className = 'title-bar-title'
// 获取子元素中 class="group-title" 的内容
const titleElement = item.querySelector('.group-title')
const titleText = titleElement ? titleElement.textContent : item.id
titleBarTitle.textContent = titleText
titleBarBox.appendChild(titleBarSlip)
titleBarBox.appendChild(titleBarTitle)
sidebarBox.appendChild(titleBarBox)
}
})
})
sidebar.appendChild(sidebarBox)
// 将侧边栏添加到页面中
SunPanelTOCDom.appendChild(sidebar)
function mobileHideSidebar() {
sidebar.classList.remove('toc-sidebar-expansion')
sidebar.classList.add('hidden')
}
function hideSidebar() {
sidebar.classList.remove('toc-sidebar-expansion')
}
function showSidebar() {
sidebar.classList.add('toc-sidebar-expansion')
sidebar.classList.remove('hidden')
}
// ----------------
// 监听宽度变化开始
// ----------------
function debounce(func, wait) {
let timeout
return function (...args) {
clearTimeout(timeout)
timeout = setTimeout(() => {
func.apply(this, args)
}, wait)
}
}
function handleResize() {
if (isMobile()) {
tocMobileBtn.classList.remove('hidden')
sidebar.classList.add('hidden')
}
else {
tocMobileBtn.classList.add('hidden')
sidebar.classList.remove('hidden')
}
}
// 使用防抖函数包装你的处理函数
const debouncedHandleResize = debounce(handleResize, 200)
// 添加事件监听器
window.addEventListener('resize', debouncedHandleResize)
// 首次触发
handleResize()
// ----------------
// 监听宽度变化结束
// ----------------
// 监听移动端按钮点击
tocMobileBtn.addEventListener('click', () => {
if (sidebar.classList.contains('toc-sidebar-expansion')) {
// 隐藏
mobileHideSidebar()
}
else {
// 显示
showSidebar()
}
})
// 监听TOC栏失去hover
sidebar.addEventListener('mouseleave', () => {
if (isMobile()) {
// 隐藏
mobileHideSidebar()
}
else {
hideSidebar()
}
})
// 监听TOC栏获得hover
sidebar.addEventListener('mouseenter', () => {
showSidebar()
})
// 监听TOC点击事件
document.querySelectorAll('.title-bar-box').forEach((box) => {
box.addEventListener('click', function (event) {
// 检查触发事件的元素是否有 'data-groupClassName' 属性
if (this.dataset.groupClassName) {
// 获取 'data-groupClass' 属性的值
const groupClassName = this.dataset.groupClassName
// 使用属性值作为选择器查询对应的元素
const targetElement = document.querySelector(`.${groupClassName}`)
if (targetElement) {
// 获取目标元素的 'top' 坐标
const targetTop = targetElement.offsetTop
const scrollContainerElement = document.querySelector(scrollContainerElementClassName)
if (scrollContainerElement) {
scrollContainerElement.scrollTo({
top: targetTop - scrollOffset,
behavior: 'smooth', // 平滑滚动
})
}
}
}
})
})
}
// 判断是否已经存在分组,不存在将定时监听
const items = document.querySelectorAll('[class*="item-group-index-"]')
if (items.length > 0) {
createDom()
return
}
const interval = setInterval(() => {
const items = document.querySelectorAll('[class*="item-group-index-"]')
if (items.length > 0) {
createDom()
clearInterval(interval)
}
}, 1000)
})()
上述代码为附件中的toc.js
文件,存放至sunpanel
安装目录的./conf/custom/
文件夹下,在前置条件给出的js
代码中// 调用函数加载多个脚本
下方增加一行'/custom/toc.js',
即可调用。
✨5.logo改自定义图片
.logo span {
display: none; /* 隐藏原有的文字 */
}
.logo {
background-image: url('/custom/logo.png'); /* 设置背景图片,上传此图片至./conf/custom/文件夹下 */
background-size: contain; /* 确保图片完整显示 */
background-repeat: no-repeat; /* 防止图片重复 */
background-position: center; /* 图片居中显示 */
width: 40%; /* 或者设置为具体的宽度 */
height: 120px; /* 或者设置为具体的高度,根据需要调整 */
}
上述代码为附件中的logo.css
文件,存放至sunpanel
安装目录的./conf/custom/
文件夹下,在前置条件给出的js
代码中// 调用函数加载多个样式表
下方增加一行'/custom/logo.css',
即可调用。
✨6.图标线条背景
/* 背景线条样式 BY 香水 [二群大佬提供] */
/* 伪元素创建背景线条样式 */
.w-full .font-semibold:before {
position: absolute; /* 设置为绝对定位 */
width: 93px; /* 宽度为93像素 */
display: block; /* 设置为块级元素 */
height: 75px; /* 高度为75像素 */
content: ""; /* 伪元素内容为空 */
border-radius: 60%; /* 边框半径为50%,形成圆形 */
z-index: -1; /* 设置层级为-1,将其放在内容之后 */
right: -27px; /* 距离右边-27像素的位置 */
top: -35px; /* 距离顶部-35像素的位置 */
background: #efcece2f; /* 背景颜色为淡白色带透明度的3b */
box-shadow: -8px 21px 0 #ceefe132; /* 设置阴影效果,水平偏移-8px,垂直偏移21px,颜色为淡白色带透明度的1a */
}
/* 伪元素创建另一种背景线条样式 */
.w-full .font-semibold:after {
position: absolute; /* 设置为绝对定位 */
width: 40px; /* 宽度为40像素 */
display: block; /* 设置为块级元素 */
height: 40px; /* 高度为40像素 */
border: 4px solid #ebece342; /* 边框为4像素的实线,颜色为淡白色带透明度的3b */
content: ""; /* 伪元素内容为空 */
border-radius: 70%; /* 边框半径为50%,形成圆形 */
top: -19px; /* 距离顶部-19像素的位置 */
right: 48px; /* 距离右边48像素的位置 */
z-index: -1; /* 设置层级为-1,将其放在内容之后 */
}
/* 设置图标信息框的圆角样式 */
.icon-info-box .rounded-2xl {
position: relative; /* 设置为相对定位 */
border-radius: 15px; /* 设置边框半径为15像素,形成圆角 */
overflow: hidden; /* 超出部分隐藏 */
-webkit-backdrop-filter: blur(10px); /* 使用Webkit前缀的背景滤镜,模糊程度为10像素 */
backdrop-filter: blur(10px); /* 背景滤镜,模糊程度为10像素 */
}
上述代码为附件中的xiantiao.css
文件,存放至sunpanel
安装目录的./conf/custom/
文件夹下,在前置条件给出的js
代码中// 调用函数加载多个样式表
下方增加一行'/custom/xiantiao.css',
即可调用。
✨7.AI小助手
loadScript('https://test.com:8888/api/application/embed?protocol=https&host=test.com:8888&token=yourtoken');
上述代码来自自行部署的MaxKB
,部署使用本教程不赘述,创建好的应用中有嵌入第三方代码,取下图中的网址替换上述代码中引号内的内容。
上述代码为附件中的ai.js
文件,存放至sunpanel
安装目录的./conf/custom/
文件夹下,在前置条件给出的js
代码中// 调用函数加载多个脚本
下方增加一行'/custom/ai.js',
即可调用。
✨8.全局字体替换
/* 自定义字体 */
@font-face {
font-family: "Font";
src: url("/custom/字体.ttf"); /* 自行下载字体存放至sunpanel安装目录的./conf/custom/文件夹下 */
}
/* 自定义全局字体 */
* {
font-family: Font;
}
上述代码为附件中的font.css
文件,存放至sunpanel
安装目录的./conf/custom/
文件夹下,在前置条件给出的js代码中// 调用函数加载多个样式表
下方增加一行'/custom/font.css',
即可调用。
✨9.飞鱼动态页脚
var RENDERER = {
POINT_INTERVAL : 5,
FISH_COUNT : 3,
MAX_INTERVAL_COUNT : 50,
INIT_HEIGHT_RATE : 0.5,
THRESHOLD : 50,
init : function(){
this.setParameters();
this.reconstructMethods();
this.setup();
this.bindEvent();
this.render();
console.log('debug');
},
setParameters : function(){
this.$window = window;
this.$document = document.body
this.$container = document.getElementById('jsi-flying-fish-container');
this.$canvas = document.createElement('canvas');
this.$container.appendChild(this.$canvas)
this.context = this.$canvas.getContext('2d');
this.points = [];
this.fishes = [];
this.watchIds = [];
},
createSurfacePoints : function(){
var count = Math.round(this.width / this.POINT_INTERVAL);
this.pointInterval = this.width / (count - 1);
this.points.push(new SURFACE_POINT(this, 0));
for(var i = 1; i < count; i++){
var point = new SURFACE_POINT(this, i * this.pointInterval),
previous = this.points[i - 1];
point.setPreviousPoint(previous);
previous.setNextPoint(point);
this.points.push(point);
}
},
reconstructMethods : function(){
this.watchWindowSize = this.watchWindowSize.bind(this);
this.jdugeToStopResize = this.jdugeToStopResize.bind(this);
this.startEpicenter = this.startEpicenter.bind(this);
this.moveEpicenter = this.moveEpicenter.bind(this);
this.reverseVertical = this.reverseVertical.bind(this);
this.render = this.render.bind(this);
},
setup : function(){
this.points.length = 0;
this.fishes.length = 0;
this.watchIds.length = 0;
this.intervalCount = this.MAX_INTERVAL_COUNT;
this.width = this.$container.offsetWidth;
this.height = this.$container.offsetHeight;
this.fishCount = this.FISH_COUNT * this.width / 500 * this.height / 500;
this.$canvas.width = this.width;
this.$canvas.height = this.height;
this.reverse = false;
this.fishes.push(new FISH(this));
this.createSurfacePoints();
},
watchWindowSize : function(){
this.clearTimer();
this.tmpWidth = this.$window.width;
this.tmpHeight = this.$window.height;
this.watchIds.push(setTimeout(this.jdugeToStopResize, this.WATCH_INTERVAL));
},
clearTimer : function(){
while(this.watchIds.length > 0){
clearTimeout(this.watchIds.pop());
}
},
jdugeToStopResize : function(){
var width = this.$window.width(),
height = this.$window.height(),
stopped = (width == this.tmpWidth && height == this.tmpHeight);
this.tmpWidth = width;
this.tmpHeight = height;
if(stopped){
this.setup();
}
},
bindEvent : function(){
this.$window.onresize = this.watchWindowSize;
this.$container.onclick = this.reverseVertical;
this.$container.onmouseenter = this.startEpicenter;
this.$container.addEventListener('onmousemove', this.moveEpicenter);
},
getAxis : function(event){
var offset = this.getOffset(this.$container);
return {
x : event.clientX - offset.left + this.$document.scrollLeft,
y : event.clientY - offset.top + this.$document.scrollTop
};
},
getOffset: function(Node, offset) {
if (!offset) {
offset = {};
offset.top = 0;
offset.left = 0;
}
if (Node == document.body) {
//当该节点为body节点时,结束递归
return offset;
}
offset.top += Node.offsetTop; offset.left += Node.offsetLeft;
return this.getOffset(Node.parentNode, offset);//向上累加offset里的值
},
startEpicenter : function(event){
this.axis = this.getAxis(event);
},
moveEpicenter : function(event){
var axis = this.getAxis(event);
if(!this.axis){
this.axis = axis;
}
this.generateEpicenter(axis.x, axis.y, axis.y - this.axis.y);
this.axis = axis;
},
generateEpicenter : function(x, y, velocity){
if(y < this.height / 2 - this.THRESHOLD || y > this.height / 2 + this.THRESHOLD){
return;
}
var index = Math.round(x / this.pointInterval);
if(index < 0 || index >= this.points.length){
return;
}
this.points[index].interfere(y, velocity);
},
reverseVertical : function(){
this.reverse = !this.reverse;
for(var i = 0, count = this.fishes.length; i < count; i++){
this.fishes[i].reverseVertical();
}
},
controlStatus : function(){
for(var i = 0, count = this.points.length; i < count; i++){
this.points[i].updateSelf();
}
for(var i = 0, count = this.points.length; i < count; i++){
this.points[i].updateNeighbors();
}
if(this.fishes.length < this.fishCount){
if(--this.intervalCount == 0){
this.intervalCount = this.MAX_INTERVAL_COUNT;
this.fishes.push(new FISH(this));
}
}
},
render : function(){
requestAnimationFrame(this.render);
this.controlStatus();
this.context.clearRect(0, 0, this.width, this.height);
this.context.fillStyle = 'hsl(0, 0%, 95%)';
for(var i = 0, count = this.fishes.length; i < count; i++){
this.fishes[i].render(this.context);
}
this.context.save();
this.context.globalCompositeOperation = 'xor';
this.context.beginPath();
this.context.moveTo(0, this.reverse ? 0 : this.height);
for(var i = 0, count = this.points.length; i < count; i++){
this.points[i].render(this.context);
}
this.context.lineTo(this.width, this.reverse ? 0 : this.height);
this.context.closePath();
this.context.fill();
this.context.restore();
}
};
var SURFACE_POINT = function(renderer, x){
this.renderer = renderer;
this.x = x;
this.init();
};
SURFACE_POINT.prototype = {
SPRING_CONSTANT : 0.03,
SPRING_FRICTION : 0.9,
WAVE_SPREAD : 0.3,
ACCELARATION_RATE : 0.01,
init : function(){
this.initHeight = this.renderer.height * this.renderer.INIT_HEIGHT_RATE;
this.height = this.initHeight;
this.fy = 0;
this.force = {previous : 0, next : 0};
},
setPreviousPoint : function(previous){
this.previous = previous;
},
setNextPoint : function(next){
this.next = next;
},
interfere : function(y, velocity){
this.fy = this.renderer.height * this.ACCELARATION_RATE * ((this.renderer.height - this.height - y) >= 0 ? -1 : 1) * Math.abs(velocity);
},
updateSelf : function(){
this.fy += this.SPRING_CONSTANT * (this.initHeight - this.height);
this.fy *= this.SPRING_FRICTION;
this.height += this.fy;
},
updateNeighbors : function(){
if(this.previous){
this.force.previous = this.WAVE_SPREAD * (this.height - this.previous.height);
}
if(this.next){
this.force.next = this.WAVE_SPREAD * (this.height - this.next.height);
}
},
render : function(context){
if(this.previous){
this.previous.height += this.force.previous;
this.previous.fy += this.force.previous;
}
if(this.next){
this.next.height += this.force.next;
this.next.fy += this.force.next;
}
context.lineTo(this.x, this.renderer.height - this.height);
}
};
var FISH = function(renderer){
this.renderer = renderer;
this.init();
};
FISH.prototype = {
GRAVITY : 0.4,
init : function(){
this.direction = Math.random() < 0.5;
this.x = this.direction ? (this.renderer.width + this.renderer.THRESHOLD) : -this.renderer.THRESHOLD;
this.previousY = this.y;
this.vx = this.getRandomValue(4, 10) * (this.direction ? -1 : 1);
if(this.renderer.reverse){
this.y = this.getRandomValue(this.renderer.height * 1 / 10, this.renderer.height * 4 / 10);
this.vy = this.getRandomValue(2, 5);
this.ay = this.getRandomValue(0.05, 0.2);
}else{
this.y = this.getRandomValue(this.renderer.height * 6 / 10, this.renderer.height * 9 / 10);
this.vy = this.getRandomValue(-5, -2);
this.ay = this.getRandomValue(-0.2, -0.05);
}
this.isOut = false;
this.theta = 0;
this.phi = 0;
},
getRandomValue : function(min, max){
return min + (max - min) * Math.random();
},
reverseVertical : function(){
this.isOut = !this.isOut;
this.ay *= -1;
},
controlStatus : function(context){
this.previousY = this.y;
this.x += this.vx;
this.y += this.vy;
this.vy += this.ay;
if(this.renderer.reverse){
if(this.y > this.renderer.height * this.renderer.INIT_HEIGHT_RATE){
this.vy -= this.GRAVITY;
this.isOut = true;
}else{
if(this.isOut){
this.ay = this.getRandomValue(0.05, 0.2);
}
this.isOut = false;
}
}else{
if(this.y < this.renderer.height * this.renderer.INIT_HEIGHT_RATE){
this.vy += this.GRAVITY;
this.isOut = true;
}else{
if(this.isOut){
this.ay = this.getRandomValue(-0.2, -0.05);
}
this.isOut = false;
}
}
if(!this.isOut){
this.theta += Math.PI / 20;
this.theta %= Math.PI * 2;
this.phi += Math.PI / 30;
this.phi %= Math.PI * 2;
}
this.renderer.generateEpicenter(this.x + (this.direction ? -1 : 1) * this.renderer.THRESHOLD, this.y, this.y - this.previousY);
if(this.vx > 0 && this.x > this.renderer.width + this.renderer.THRESHOLD || this.vx < 0 && this.x < -this.renderer.THRESHOLD){
this.init();
}
},
render : function(context){
context.save();
context.translate(this.x, this.y);
context.rotate(Math.PI + Math.atan2(this.vy, this.vx));
context.scale(1, this.direction ? 1 : -1);
context.beginPath();
context.moveTo(-30, 0);
context.bezierCurveTo(-20, 15, 15, 10, 40, 0);
context.bezierCurveTo(15, -10, -20, -15, -30, 0);
context.fill();
context.save();
context.translate(40, 0);
context.scale(0.9 + 0.2 * Math.sin(this.theta), 1);
context.beginPath();
context.moveTo(0, 0);
context.quadraticCurveTo(5, 10, 20, 8);
context.quadraticCurveTo(12, 5, 10, 0);
context.quadraticCurveTo(12, -5, 20, -8);
context.quadraticCurveTo(5, -10, 0, 0);
context.fill();
context.restore();
context.save();
context.translate(-3, 0);
context.rotate((Math.PI / 3 + Math.PI / 10 * Math.sin(this.phi)) * (this.renderer.reverse ? -1 : 1));
context.beginPath();
if(this.renderer.reverse){
context.moveTo(5, 0);
context.bezierCurveTo(10, 10, 10, 30, 0, 40);
context.bezierCurveTo(-12, 25, -8, 10, 0, 0);
}else{
context.moveTo(-5, 0);
context.bezierCurveTo(-10, -10, -10, -30, 0, -40);
context.bezierCurveTo(12, -25, 8, -10, 0, 0);
}
context.closePath();
context.fill();
context.restore();
context.restore();
this.controlStatus(context);
}
};
document.addEventListener('DOMContentLoaded', (event) => {
// 用于添加视频背景的函数
const addFishBackground = (wallpaperDiv) => {
// 创建一个新的div元素
var newDiv = document.createElement("div");
// 设置div的id属性
newDiv.setAttribute("id", "jsi-flying-fish-container");
// 设置div的class属性
newDiv.setAttribute("class", "fishcontainer");
// 设置div的样式
newDiv.style.width = "100%";
newDiv.style.height = "200px";
newDiv.style.position = "fixed";
newDiv.style.zIndex = "0";
newDiv.style.opacity = "0.37";
newDiv.style.bottom = "0";
newDiv.style.left = "0";
// 将新创建的div元素添加到body中
wallpaperDiv.appendChild(newDiv);
};
// 使用MutationObserver监视DOM变化
const observer = new MutationObserver((mutationsList, observer) => {
// 查找匹配的.cover.wallpaper元素
const wallpaperDiv = document.querySelector('.cover.wallpaper');
if (wallpaperDiv && !wallpaperDiv.querySelector('.fishcontainer')) {
// 添加视频背景
addFishBackground(wallpaperDiv);
// 注意:我们不再断开观察者,以便它能够继续监视未来的变化
RENDERER.init();
}
});
// 启动观察者监视document.body的变化
observer.observe(document.body, { childList: true, subtree: true });
});
上述代码为附件中的fishbackground.js
文件,存放至sunpanel
安装目录的./conf/custom/
文件夹下,在前置条件给出的js
代码中// 调用函数加载多个脚本
下方增加一行'/custom/fishbackground.js',
即可调用。
✨10.搜索栏下小组件(仅供参考,自行研究)
此方案需要配合自定义页脚来实现,下面是我在用的页脚,感兴趣的可以研究下,不是太完美。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
body, html {
margin: 0;
padding: 0;
}
.site-footer {
padding: 30px 0;
text-align: center;
color: #fff;
}
.footer-content {
display: flex;
justify-content: center;
}
.footer-links {
margin: 0;
padding: 0;
list-style: none;
}
.footer-links li {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.footer-links a {
text-decoration: none;
color: inherit;
transition: color 0.3s ease;
display: flex;
align-items: center;
}
.footer-links a:hover {
color: #ccc;
}
.footer-icon {
width: 35px;
height: auto;
margin-right: 15px;
}
.row {
display: flex;
flex-wrap: wrap; /* 允许换行 */
justify-content: space-around; /* 平均分布 */
width: 100%; /* 确保容器宽度 */
margin-top: 20px; /* 在.row组件上方添加间距 */
}
.widget {
flex: 1 1 100px; /* 允许组件扩展和收缩,最小宽度为300px */
margin: 10px; /* 添加间距防止紧贴 */
box-sizing: border-box; /* 确保padding和border包含在宽度内 */
}
.responsive-widget {
width: 100%;
margin-top: 20px; /* 在.responsive-widget组件上方添加间距 */
}
#ww_17bfa75f2cb5c_u, #ww_df9fcd9873fd2_u {
display: none;
}
</style>
</head>
<body>
<div class="spacer"></div> <!-- 上方空白空间 -->
<div class="row">
<!-- 异步加载天气组件脚本 -->
<div id="ww_17bfa75f2cb5c" v='1.3' loc='id' a='{"t":"horizontal","lang":"zh","sl_lpl":1,"ids":["wl11510"],"font":"Arial","sl_ics":"one_a","sl_sot":"celsius","cl_bkg":"#FFFFFF00","cl_font":"rgba(255,255,255,1)","cl_cloud":"rgba(255,255,255,1)","cl_persp":"#2196F3","cl_sun":"#FFC107","cl_moon":"#FFC107","cl_thund":"#FF5722","el_nme":3}'>
<a href="https://weatherwidget.org/" id="ww_17bfa75f2cb5c_u" target="_blank">Weather widget for website</a>
<script async src="https://app3.weatherwidget.org/js/?id=ww_17bfa75f2cb5c"></script>
</div>
<!-- 使用loading="lazy"属性优化iframe加载 -->
<iframe src="https://www.widgets.link/#/tools-hot-news?contentBoxShadowColor=FFFFFF0D&ac=F0B17F00&tc=19A7CE8C&ttc=ffffff&tic=ffffff&thc=ffffff&cc=FBE8D900&bg=&_b=true" class="widget" width="1500" height="210" loading="lazy"></iframe>
<iframe src="https://www.widgets.link/#/typed?t=Hi,这里是Coco的导航页&bg=&tf=28&tt=ffffff&s=40&p=10&br=20&_b=true&pbg=FFFFFF00&bs=true&cf=16&cc=EEE8E8FF&c=随意享用吧~~" class="widget" width="33300" height="200" loading="lazy"></iframe>
</div>
<!-- 新增的天气组件,单独占据一行 -->
<div class="responsive-widget">
<div id="ww_df9fcd9873fd2" v='1.3' loc='id' a='{"t":"responsive","lang":"zh","sl_lpl":1,"ids":["wl11510"],"font":"Arial","sl_ics":"one_a","sl_sot":"celsius","cl_bkg":"#FFFFFF00","cl_font":"rgba(255,255,255,1)","cl_cloud":"rgba(255,255,255,1)","cl_persp":"#2196F3","cl_sun":"#FFC107","cl_moon":"#FFC107","cl_thund":"#FF5722","el_nme":3,"el_ctm":3,"el_cwi":3}'>
<a href="https://weatherwidget.org/" id="ww_df9fcd9873fd2_u" target="_blank">Weather widget for website</a>
<script async src="https://app3.weatherwidget.org/js/?id=ww_df9fcd9873fd2"></script>
</div>
</div>
<footer class="site-footer">
<div class="footer-content">
<ul class="footer-links">
<li></li>
<li>
<a href="https://cocohe.cn" target="_blank" class="personal-link">
<img class="footer-icon" src="/uploads/2024/8/10/1985fc970e85cddfdb818d5d174fbde7.ico" alt="可可同学图标">
<span><font size="4" color=" #fff" style="font-family: 'STCaiyun'"><b>@ 可可同学</font></b></span>
</a>
</li>
</ul>
</div>
</footer>
<script>
// 获取目标元素
var itemCardBox = document.getElementById("item-card-box");
// 获取要插入的组件
var weatherComponent1 = document.querySelector('.row');
var weatherComponent = document.querySelector('.responsive-widget');
// 检查目标元素是否存在
if (itemCardBox) {
// 在目标元素上方插入组件
itemCardBox.insertAdjacentElement('beforebegin', weatherComponent1);
itemCardBox.insertAdjacentElement('beforebegin', weatherComponent);
} else {
console.warn('目标元素未找到: item-card-box');
}
</script>
</body>
</html>
上述页脚中包含了两个天气组件,来自免费天气插件:https://weatherwidget.org/zh/,其中包含了自定义位置,大家需要去根据地址自己生成一下插件,对应修改下页脚中的代码内容,还包括了新闻组件和打字机组件,来自轻轻小组件:https://www.widgets.link/#/,可以自己挑选喜欢的组件修改对应页脚代码。
上述代码为附件中的页脚.html
文件,记事本或VSCode
打开后复制至自定义页脚输入框即可调用。
✨11.特殊:小鱼页脚和搜索栏下小组件完美共存方案
上面的方案中,如果同时使用小鱼页脚和搜索栏下小组件的话,小鱼页脚经常难以加载,最近终于找到了解决方案!主要是由于搜索栏下小组件影响了DOM
事件,这样修改可以使小鱼页脚js
脚本优先加载,后续遇到类似的情况应该也可以这样解决。
多JS、CSS调用脚本
中直接加入小鱼页脚js
代码,可放置在不影响加载效果的地方,我这里使用了动态背景,所以放在了动态背景下面,不然的话打开的时候会黑一下,大家根据自己的实际情况调整即可。
// 加载脚本函数
function loadScript(url) {
const script = document.createElement('script');
script.src = url;
script.type = 'text/javascript';
document.head.appendChild(script);
}
// 加载样式表函数
function loadStyle(url) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
document.head.appendChild(link);
}
// 调用函数加载多个样式表
loadStyle('/custom/bk.css');
loadStyle('/custom/action.css');
loadStyle('/custom/logo.css');
loadStyle('/custom/xiantiao.css');
//loadStyle('/custom/待添加.css');
var RENDERER = {
POINT_INTERVAL : 5,
FISH_COUNT : 3,
MAX_INTERVAL_COUNT : 50,
INIT_HEIGHT_RATE : 0.5,
THRESHOLD : 50,
init : function(){
this.setParameters();
this.reconstructMethods();
this.setup();
this.bindEvent();
this.render();
console.log('debug');
},
setParameters : function(){
this.$window = window;
this.$document = document.body
this.$container = document.getElementById('jsi-flying-fish-container');
this.$canvas = document.createElement('canvas');
this.$container.appendChild(this.$canvas)
this.context = this.$canvas.getContext('2d');
this.points = [];
this.fishes = [];
this.watchIds = [];
},
createSurfacePoints : function(){
var count = Math.round(this.width / this.POINT_INTERVAL);
this.pointInterval = this.width / (count - 1);
this.points.push(new SURFACE_POINT(this, 0));
for(var i = 1; i < count; i++){
var point = new SURFACE_POINT(this, i * this.pointInterval),
previous = this.points[i - 1];
point.setPreviousPoint(previous);
previous.setNextPoint(point);
this.points.push(point);
}
},
reconstructMethods : function(){
this.watchWindowSize = this.watchWindowSize.bind(this);
this.jdugeToStopResize = this.jdugeToStopResize.bind(this);
this.startEpicenter = this.startEpicenter.bind(this);
this.moveEpicenter = this.moveEpicenter.bind(this);
this.reverseVertical = this.reverseVertical.bind(this);
this.render = this.render.bind(this);
},
setup : function(){
this.points.length = 0;
this.fishes.length = 0;
this.watchIds.length = 0;
this.intervalCount = this.MAX_INTERVAL_COUNT;
this.width = this.$container.offsetWidth;
this.height = this.$container.offsetHeight;
this.fishCount = this.FISH_COUNT * this.width / 500 * this.height / 500;
this.$canvas.width = this.width;
this.$canvas.height = this.height;
this.reverse = false;
this.fishes.push(new FISH(this));
this.createSurfacePoints();
},
watchWindowSize : function(){
this.clearTimer();
this.tmpWidth = this.$window.width;
this.tmpHeight = this.$window.height;
this.watchIds.push(setTimeout(this.jdugeToStopResize, this.WATCH_INTERVAL));
},
clearTimer : function(){
while(this.watchIds.length > 0){
clearTimeout(this.watchIds.pop());
}
},
jdugeToStopResize : function(){
var width = this.$window.width(),
height = this.$window.height(),
stopped = (width == this.tmpWidth && height == this.tmpHeight);
this.tmpWidth = width;
this.tmpHeight = height;
if(stopped){
this.setup();
}
},
bindEvent : function(){
this.$window.onresize = this.watchWindowSize;
this.$container.onclick = this.reverseVertical;
this.$container.onmouseenter = this.startEpicenter;
this.$container.addEventListener('onmousemove', this.moveEpicenter);
},
getAxis : function(event){
var offset = this.getOffset(this.$container);
return {
x : event.clientX - offset.left + this.$document.scrollLeft,
y : event.clientY - offset.top + this.$document.scrollTop
};
},
getOffset: function(Node, offset) {
if (!offset) {
offset = {};
offset.top = 0;
offset.left = 0;
}
if (Node == document.body) {
//当该节点为body节点时,结束递归
return offset;
}
offset.top += Node.offsetTop; offset.left += Node.offsetLeft;
return this.getOffset(Node.parentNode, offset);//向上累加offset里的值
},
startEpicenter : function(event){
this.axis = this.getAxis(event);
},
moveEpicenter : function(event){
var axis = this.getAxis(event);
if(!this.axis){
this.axis = axis;
}
this.generateEpicenter(axis.x, axis.y, axis.y - this.axis.y);
this.axis = axis;
},
generateEpicenter : function(x, y, velocity){
if(y < this.height / 2 - this.THRESHOLD || y > this.height / 2 + this.THRESHOLD){
return;
}
var index = Math.round(x / this.pointInterval);
if(index < 0 || index >= this.points.length){
return;
}
this.points[index].interfere(y, velocity);
},
reverseVertical : function(){
this.reverse = !this.reverse;
for(var i = 0, count = this.fishes.length; i < count; i++){
this.fishes[i].reverseVertical();
}
},
controlStatus : function(){
for(var i = 0, count = this.points.length; i < count; i++){
this.points[i].updateSelf();
}
for(var i = 0, count = this.points.length; i < count; i++){
this.points[i].updateNeighbors();
}
if(this.fishes.length < this.fishCount){
if(--this.intervalCount == 0){
this.intervalCount = this.MAX_INTERVAL_COUNT;
this.fishes.push(new FISH(this));
}
}
},
render : function(){
requestAnimationFrame(this.render);
this.controlStatus();
this.context.clearRect(0, 0, this.width, this.height);
this.context.fillStyle = 'hsl(0, 0%, 95%)';
for(var i = 0, count = this.fishes.length; i < count; i++){
this.fishes[i].render(this.context);
}
this.context.save();
this.context.globalCompositeOperation = 'xor';
this.context.beginPath();
this.context.moveTo(0, this.reverse ? 0 : this.height);
for(var i = 0, count = this.points.length; i < count; i++){
this.points[i].render(this.context);
}
this.context.lineTo(this.width, this.reverse ? 0 : this.height);
this.context.closePath();
this.context.fill();
this.context.restore();
}
};
var SURFACE_POINT = function(renderer, x){
this.renderer = renderer;
this.x = x;
this.init();
};
SURFACE_POINT.prototype = {
SPRING_CONSTANT : 0.03,
SPRING_FRICTION : 0.9,
WAVE_SPREAD : 0.3,
ACCELARATION_RATE : 0.01,
init : function(){
this.initHeight = this.renderer.height * this.renderer.INIT_HEIGHT_RATE;
this.height = this.initHeight;
this.fy = 0;
this.force = {previous : 0, next : 0};
},
setPreviousPoint : function(previous){
this.previous = previous;
},
setNextPoint : function(next){
this.next = next;
},
interfere : function(y, velocity){
this.fy = this.renderer.height * this.ACCELARATION_RATE * ((this.renderer.height - this.height - y) >= 0 ? -1 : 1) * Math.abs(velocity);
},
updateSelf : function(){
this.fy += this.SPRING_CONSTANT * (this.initHeight - this.height);
this.fy *= this.SPRING_FRICTION;
this.height += this.fy;
},
updateNeighbors : function(){
if(this.previous){
this.force.previous = this.WAVE_SPREAD * (this.height - this.previous.height);
}
if(this.next){
this.force.next = this.WAVE_SPREAD * (this.height - this.next.height);
}
},
render : function(context){
if(this.previous){
this.previous.height += this.force.previous;
this.previous.fy += this.force.previous;
}
if(this.next){
this.next.height += this.force.next;
this.next.fy += this.force.next;
}
context.lineTo(this.x, this.renderer.height - this.height);
}
};
var FISH = function(renderer){
this.renderer = renderer;
this.init();
};
FISH.prototype = {
GRAVITY : 0.4,
init : function(){
this.direction = Math.random() < 0.5;
this.x = this.direction ? (this.renderer.width + this.renderer.THRESHOLD) : -this.renderer.THRESHOLD;
this.previousY = this.y;
this.vx = this.getRandomValue(4, 10) * (this.direction ? -1 : 1);
if(this.renderer.reverse){
this.y = this.getRandomValue(this.renderer.height * 1 / 10, this.renderer.height * 4 / 10);
this.vy = this.getRandomValue(2, 5);
this.ay = this.getRandomValue(0.05, 0.2);
}else{
this.y = this.getRandomValue(this.renderer.height * 6 / 10, this.renderer.height * 9 / 10);
this.vy = this.getRandomValue(-5, -2);
this.ay = this.getRandomValue(-0.2, -0.05);
}
this.isOut = false;
this.theta = 0;
this.phi = 0;
},
getRandomValue : function(min, max){
return min + (max - min) * Math.random();
},
reverseVertical : function(){
this.isOut = !this.isOut;
this.ay *= -1;
},
controlStatus : function(context){
this.previousY = this.y;
this.x += this.vx;
this.y += this.vy;
this.vy += this.ay;
if(this.renderer.reverse){
if(this.y > this.renderer.height * this.renderer.INIT_HEIGHT_RATE){
this.vy -= this.GRAVITY;
this.isOut = true;
}else{
if(this.isOut){
this.ay = this.getRandomValue(0.05, 0.2);
}
this.isOut = false;
}
}else{
if(this.y < this.renderer.height * this.renderer.INIT_HEIGHT_RATE){
this.vy += this.GRAVITY;
this.isOut = true;
}else{
if(this.isOut){
this.ay = this.getRandomValue(-0.2, -0.05);
}
this.isOut = false;
}
}
if(!this.isOut){
this.theta += Math.PI / 20;
this.theta %= Math.PI * 2;
this.phi += Math.PI / 30;
this.phi %= Math.PI * 2;
}
this.renderer.generateEpicenter(this.x + (this.direction ? -1 : 1) * this.renderer.THRESHOLD, this.y, this.y - this.previousY);
if(this.vx > 0 && this.x > this.renderer.width + this.renderer.THRESHOLD || this.vx < 0 && this.x < -this.renderer.THRESHOLD){
this.init();
}
},
render : function(context){
context.save();
context.translate(this.x, this.y);
context.rotate(Math.PI + Math.atan2(this.vy, this.vx));
context.scale(1, this.direction ? 1 : -1);
context.beginPath();
context.moveTo(-30, 0);
context.bezierCurveTo(-20, 15, 15, 10, 40, 0);
context.bezierCurveTo(15, -10, -20, -15, -30, 0);
context.fill();
context.save();
context.translate(40, 0);
context.scale(0.9 + 0.2 * Math.sin(this.theta), 1);
context.beginPath();
context.moveTo(0, 0);
context.quadraticCurveTo(5, 10, 20, 8);
context.quadraticCurveTo(12, 5, 10, 0);
context.quadraticCurveTo(12, -5, 20, -8);
context.quadraticCurveTo(5, -10, 0, 0);
context.fill();
context.restore();
context.save();
context.translate(-3, 0);
context.rotate((Math.PI / 3 + Math.PI / 10 * Math.sin(this.phi)) * (this.renderer.reverse ? -1 : 1));
context.beginPath();
if(this.renderer.reverse){
context.moveTo(5, 0);
context.bezierCurveTo(10, 10, 10, 30, 0, 40);
context.bezierCurveTo(-12, 25, -8, 10, 0, 0);
}else{
context.moveTo(-5, 0);
context.bezierCurveTo(-10, -10, -10, -30, 0, -40);
context.bezierCurveTo(12, -25, 8, -10, 0, 0);
}
context.closePath();
context.fill();
context.restore();
context.restore();
this.controlStatus(context);
}
};
document.addEventListener('DOMContentLoaded', (event) => {
// 用于添加视频背景的函数
const addFishBackground = (wallpaperDiv) => {
// 创建一个新的div元素
var newDiv = document.createElement("div");
// 设置div的id属性
newDiv.setAttribute("id", "jsi-flying-fish-container");
// 设置div的class属性
newDiv.setAttribute("class", "fishcontainer");
// 设置div的样式
newDiv.style.width = "100%";
newDiv.style.height = "200px";
newDiv.style.position = "fixed";
newDiv.style.zIndex = "0";
newDiv.style.opacity = "0.37";
newDiv.style.bottom = "0";
newDiv.style.left = "0";
// 将新创建的div元素添加到body中
wallpaperDiv.appendChild(newDiv);
};
// 使用MutationObserver监视DOM变化
const observer = new MutationObserver((mutationsList, observer) => {
// 查找匹配的.cover.wallpaper元素
const wallpaperDiv = document.querySelector('.cover.wallpaper');
if (wallpaperDiv && !wallpaperDiv.querySelector('.fishcontainer')) {
// 添加视频背景
addFishBackground(wallpaperDiv);
// 注意:我们不再断开观察者,以便它能够继续监视未来的变化
RENDERER.init();
}
});
// 启动观察者监视document.body的变化
observer.observe(document.body, { childList: true, subtree: true });
});
// 调用函数加载多个脚本
//loadScript('/custom/fishbackground.js');
loadScript('/custom/ai.js');
loadScript('/custom/toc.js');
//loadStyle('/custom/待添加.js');
♦️上述代码为附件all(小鱼页脚和小组件共存版).js
文件,记事本或VSCode
等软件打开后复制到上述JS
脚本输入框即可,需要什么美化,就自己修改下调用路径(CSS
在小鱼页脚代码的上面)。
✨12.搜索栏文字修改为自动更新一言
// 定义一个函数,用于获取随机句子并更新占位符
function updatePlaceholder() {
fetch('https://v1.hitokoto.cn/')
.then(response => response.json())
.then(data => {
// 查找所有输入框
const inputElements = document.querySelectorAll('input[placeholder="请输入搜索内容"]');
if (inputElements.length > 0) {
// 遍历所有找到的输入框并更新占位符
inputElements.forEach(input => {
input.placeholder = data.hitokoto;
});
}
})
.catch(error => {
console.error('获取句子时出错:', error);
});
}
// 页面加载时自动调用替换函数
window.onload = updatePlaceholder;
♦️上述代码为附件中的yiyan.js
文件,存放至sunpanel
安装目录的./conf/custom/
文件夹下,在前置条件给出的js
代码中// 调用函数加载多个脚本
下方增加一行'/custom/ai.js',
即可调用。
评论区