完善界面UI添加主题选项,修复BUG

This commit is contained in:
171696257
2025-05-27 15:08:55 +08:00
parent a5eeb2596b
commit a79008fa1a
11 changed files with 3641 additions and 547 deletions
+6 -1
View File
@@ -88,7 +88,9 @@ High-performance model loading optimizer with multiple optimization modes:
<details>
<summary>DD Interface Layout</summary>
- Alpha version implemented, can randomly or specifically color node titles, and quickly organize messy workflows into modules
- Beta version implemented, can randomly or specifically color node titles, and quickly organize messy workflows.
- Beautified UI, added theme system, each theme has two different styles to switch between, supports custom themes.
- Added modular presets, you can preset your own layouts and apply them with one click.
</details>
@@ -119,6 +121,9 @@ This extension now supports ComfyUI's native internationalization (i18n) system,
3. Currently only supports node translation, frontend functionality translation is not yet supported
## Version History
- v2.3.1 (2025-05-27)
- Improved UI, added theme options, and fixed bugs.
- v2.3.0 (2025-05-14)
- Interface Layout Alpha 2.0 version released, with improved interface and basic tools
- Now you can synchronize the width, height, and size of selected nodes, and align all selected nodes
+10 -2
View File
@@ -90,7 +90,9 @@
<details>
<summary>DD 界面布局</summary>
-已实现Alpha版本,可以随机或指定节点标题的颜色,可以对杂乱工作流做快速的模块化分类整理
- 已实现Bate版本,可以随机或指定节点标题的颜色,可以对杂乱节点做快速整理
- 美化UI,添加主题系统,每一种主题有两种不同风格可以切换,支持自定义主题
- 添加模块化预设,可以预设自己的布局,一键应用
</details>
@@ -110,7 +112,10 @@
3. 重启 ComfyUI
方法2(推荐)
使用Manager或启动器进行安装
使用Manager或启动器通过git进行安装
方法3(推荐)
直接在Manager中搜索插件名称安装
## 语言切换
@@ -121,6 +126,9 @@
3. 暂时只支持节点翻译,暂不支持前端功能翻译切换
## 版本历史
- v2.3.1 (2025-05-27)
-完善界面UI添加主题选项,修复BUG。
- v2.3.0 (2025-05-14)
- 界面布局Alpha2.0版本上线,完善界面和添加基本工具,现在可以同步选中节点的宽高与大小,对齐所有的选中节点。
- 依然是测试版本,如果有BUG请及时反馈。
+246 -521
View File
@@ -1,73 +1,40 @@
// 智能布局核心模块
// 布局面板相关样式
const layoutPanelStyles = `
.layout-panel {
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
/* 按钮彩虹动画 */
@keyframes rainbow {
0% { background-position: 0% 82% }
50% { background-position: 100% 19% }
100% { background-position: 0% 82% }
}
.layout-btn-rainbow:hover {
background-image: linear-gradient(124deg, #ff2400, #e81d1d, #e8b71d, #e3e81d, #1de840, #1ddde8, #2b1de8, #dd00f3, #dd00f3) !important;
background-size: 1800% 1800% !important;
animation: rainbow 6s ease infinite !important;
}
/* 按钮水波纹效果 */
.layout-btn::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 5px;
height: 5px;
background: rgba(255, 255, 255, .5);
opacity: 0;
border-radius: 100%;
transform: scale(1, 1) translate(-50%, -50%);
transform-origin: 50% 50%;
}
.layout-btn:active::after {
animation: ripple 0.6s ease-out;
}
@keyframes ripple {
0% {
transform: scale(0, 0) translate(-50%, -50%);
opacity: 0.5;
}
100% {
transform: scale(20, 20) translate(-50%, -50%);
opacity: 0;
}
}
`;
// 导入统一样式模块
import {
layoutPanelStyles,
injectStyles,
removeStyles,
createPanelElement,
createButtonsContainer,
createButton,
createModeContainer,
createSelectBox,
addOptionsToSelect,
setPanelBackgroundOpacity,
setButtonsOpacity,
showNotification,
adjustButtonsLayout,
registerTheme,
getRegisteredThemes,
getTheme,
applyTheme,
setDefaultTheme,
getDefaultTheme,
createCoinElement,
showAtMousePosition
} from './styles/UIStyles.js';
// 工具函数:向页面注入样式
function injectStyles(styleId, styleContent) {
// 如果已经存在相同ID的样式,则不重复注入
if (document.getElementById(styleId)) return;
const styleElement = document.createElement('style');
styleElement.id = styleId;
styleElement.textContent = styleContent;
document.head.appendChild(styleElement);
}
// 工具函数:移除已注入的样式
function removeStyles(styleId) {
const styleElement = document.getElementById(styleId);
if (styleElement && styleElement.parentNode) {
styleElement.parentNode.removeChild(styleElement);
}
}
// 导入通用工具函数 - 修改导入路径为styles文件夹
import {
getComfyUIApp,
getSelectedNodes,
getSelectedGroups,
getRandomColor,
syncNodeSize,
syncNodeWidthHeight,
alignNodes
} from './styles/UIUtils.js';
// 跟踪鼠标位置
let mousePosition = { x: 0, y: 0 };
@@ -75,12 +42,15 @@ document.addEventListener('mousemove', (e) => {
mousePosition = { x: e.clientX, y: e.clientY };
});
export class LayoutPanel { constructor() {
export class LayoutPanel {
constructor() {
this.shortcut = 'alt+x';
this.visible = false;
this.container = null;
this.buttonsContainer = null;
this.colorMode = '完全随机'; // 默认为完全随机模式
this.currentTheme = null;
this.coinElement = null;
}
setEnabled() {
@@ -96,6 +66,31 @@ export class LayoutPanel { constructor() {
}
}
// 设置当前主题
setTheme(themeId) {
if (!this.container) {
this._createPanel();
}
// 保存当前的透明度设置,以便应用主题后恢复
const bgOpacity = this.container.dataset.bgOpacity;
const btnOpacity = this.container.dataset.btnOpacity;
// 应用新主题
if (applyTheme(themeId, this.container, this.coinElement)) {
this.currentTheme = themeId;
// 主题应用后,恢复保存的透明度设置
setTimeout(() => {
if (bgOpacity) this.setOpacity(parseFloat(bgOpacity) * 100);
if (btnOpacity) this.setButtonOpacity(parseFloat(btnOpacity) * 100);
}, 50); // 短延迟确保主题样式完全应用
return true;
}
return false;
}
// 只有通过toggle方法才能控制面板显示/隐藏,确保只能通过快捷键激活
toggle() {
if (this.visible) {
@@ -110,35 +105,25 @@ export class LayoutPanel { constructor() {
this._createPanel();
}
// 使用当前鼠标位置
this._showAtMousePosition();
// 使用当前鼠标位置显示面板
showAtMousePosition(this.container, mousePosition);
this.container.style.display = 'flex';
this.container.style.pointerEvents = 'auto'; // 显示时允许事件
// 使用动画效果显示
// 使用动画效果显示 - 修改:使用 opacity 动画,不再修改 transform
requestAnimationFrame(() => {
this.container.style.opacity = '1';
this.container.style.transform = 'scale(1)';
});
this.visible = true;
}
// 在鼠标位置显示面板
_showAtMousePosition() {
if (!this.container) return;
// 调整为居中显示
this.container.style.left = `${mousePosition.x}px`;
this.container.style.top = `${mousePosition.y}px`;
this.container.style.transform = 'translate(-50%, -50%)';
}
hide() {
if (!this.container) return;
// 只淡出,不修改 transform
this.container.style.opacity = '0';
this.container.style.transform = 'translate(-50%, -50%) scale(0.95)';
this.container.style.pointerEvents = 'none'; // 隐藏时禁止事件
setTimeout(() => {
@@ -153,105 +138,102 @@ export class LayoutPanel { constructor() {
setOpacity(opacity) {
// opacity: 0~100
if (!this.container) return;
const percent = Math.max(0, Math.min(100, Number(opacity)));
this.container.style.backgroundColor = `rgba(30, 30, 40, ${percent/100})`;
setPanelBackgroundOpacity(this.container, opacity);
}
// 添加新方法:设置按钮透明度
setButtonOpacity(opacity) {
// opacity: 0~100
if (!this.container) return;
setButtonsOpacity(this.container, opacity);
}
_createPanel() {
if (this.container) return;
// 创建容器
this.container = document.createElement('div');
this.container.className = 'layout-panel';
// 只设置一次 position
this.container.style.position = 'fixed';
this.container.style.left = `${mousePosition.x}px`;
this.container.style.top = `${mousePosition.y}px`;
this.container.style.transform = 'translate(-50%, -50%)';
this.container.style.zIndex = 99999;
this.container.style.display = 'none';
this.container.style.pointerEvents = 'none'; // 默认禁用事件,确保只有在面板显示时才能交互
this.container.style.transition = 'opacity 0.3s, transform 0.3s';
this.container.style.opacity = '0';
// 更现代的面板样式
this.container.style.padding = '20px';
this.container.style.borderRadius = '16px';
this.container.style.backdropFilter = 'blur(12px)';
this.container.style.boxShadow = '0 8px 32px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.1) inset';
// 移除 backgroundColor 的硬编码,交由 setOpacity 控制
this.container.style.border = '1px solid rgba(255, 255, 255, 0.1)';
this.container.style.flexDirection = 'column';
this.container.style.alignItems = 'center';
this.container.style.gap = '16px';
// 使用外部样式模块创建面板
this.container = createPanelElement(mousePosition);
// 存储初始透明度值到容器数据属性中,便于后续恢复
this.container.dataset.initialSetup = 'pending';
// 初始化透明度为全局设置值
let opacity = 85;
let buttonOpacity = 90;
try {
if (window.app?.extensionManager?.setting) {
opacity = window.app.extensionManager.setting.get("LayoutPanel.opacity", 85);
buttonOpacity = window.app.extensionManager.setting.get("LayoutPanel.buttonOpacity", 90);
}
} catch(e) {}
this.setOpacity(opacity);
// 监听设置变化(如果有API)
} catch(e) {
console.warn("获取透明度设置失败:", e);
}
// 将初始透明度值存储到数据属性
this.container.dataset.initialBgOpacity = opacity / 100;
this.container.dataset.initialBtnOpacity = buttonOpacity / 100;
// 预先保存透明度值到数据属性,但延迟应用
// 这样可以确保主题应用后再设置透明度
this.container.dataset.bgOpacity = opacity / 100;
this.container.dataset.btnOpacity = buttonOpacity / 100;
// 确保面板存在后才设置透明度
setTimeout(() => {
// 确保面板已经完全创建并添加到DOM中
this.setOpacity(opacity);
this.setButtonOpacity(buttonOpacity);
}, 50);
// 监听设置变化
if (window.app?.extensionManager?.setting?.onChange) {
window.app.extensionManager.setting.onChange("LayoutPanel.opacity", (val) => {
this.setOpacity(val);
});
window.app.extensionManager.setting.onChange("LayoutPanel.buttonOpacity", (val) => {
this.setButtonOpacity(val);
});
}
// 创建按钮容器 - 居中对齐
this.buttonsContainer = document.createElement('div');
this.buttonsContainer.className = 'layout-buttons-container';
this.buttonsContainer.style.display = 'flex';
this.buttonsContainer.style.justifyContent = 'center'; // 确保按钮居中
this.buttonsContainer.style.width = '100%';
this.buttonsContainer.style.gap = '12px';
this.buttonsContainer.style.marginTop = '4px';
// 创建硬币容器
this.coinElement = createCoinElement();
// 创建随机颜色按钮
const randomColorBtn = this._createButton('随机颜色', 'rainbow');
// 确保硬币元素添加到面板顶部
if (this.container.firstChild) {
this.container.insertBefore(this.coinElement, this.container.firstChild);
} else {
this.container.appendChild(this.coinElement);
}
// 创建按钮容器
this.buttonsContainer = createButtonsContainer();
// 所有按钮使用统一的创建和样式方法
const randomColorBtn = createButton('随机颜色', 'normal');
randomColorBtn.addEventListener('click', (e) => {
e.stopPropagation();
this._setRandomNodeColor();
});
// 创建自定义颜色按钮
const customColorBtn = this._createButton('自定义颜色', 'pick');
const customColorBtn = createButton('自定义颜色', 'normal');
customColorBtn.addEventListener('click', (e) => {
e.stopPropagation();
this._openCustomColorPicker();
});
// 创建节点同步按钮
const syncBtn = this._createButton('节点同步', 'sync', '#2db7f5');
const syncBtn = createButton('节点同步', 'normal');
// 节点同步模式选择器
const syncModeContainer = document.createElement('div');
syncModeContainer.style.display = 'flex';
syncModeContainer.style.flexDirection = 'column';
syncModeContainer.style.width = '100%';
syncModeContainer.style.marginTop = '10px';
syncModeContainer.style.gap = '8px';
const syncModeLabel = document.createElement('div');
syncModeLabel.textContent = '节点同步模式:';
syncModeLabel.style.color = '#e1e1e1';
syncModeLabel.style.fontSize = '14px';
syncModeLabel.style.fontWeight = '500';
const syncModeSelect = document.createElement('select');
syncModeSelect.style.width = '100%';
syncModeSelect.style.padding = '6px 10px';
syncModeSelect.style.backgroundColor = 'rgba(45, 45, 55, 0.8)';
syncModeSelect.style.color = '#e1e1e1';
syncModeSelect.style.border = '1px solid rgba(255, 255, 255, 0.2)';
syncModeSelect.style.borderRadius = '6px';
syncModeSelect.style.outline = 'none';
[
const { modeContainer: syncModeContainer, modeLabel: syncModeLabel } = createModeContainer('节点同步模式:');
const syncModeSelect = createSelectBox();
addOptionsToSelect(syncModeSelect, [
{ value: 'size', text: '同步大小' },
{ value: 'wh', text: '同步宽高' },
{ value: 'align', text: '同步对齐' }
].forEach(option => {
const opt = document.createElement('option');
opt.value = option.value;
opt.textContent = option.text;
syncModeSelect.appendChild(opt);
});
syncModeContainer.appendChild(syncModeLabel);
]);
syncModeContainer.appendChild(syncModeSelect);
syncBtn.addEventListener('click', (e) => {
e.stopPropagation();
@@ -259,47 +241,24 @@ export class LayoutPanel { constructor() {
});
// 创建颜色模式选择器
const modeContainer = document.createElement('div');
modeContainer.style.display = 'flex';
modeContainer.style.flexDirection = 'column';
modeContainer.style.width = '100%';
modeContainer.style.marginTop = '10px';
modeContainer.style.gap = '8px';
const modeLabel = document.createElement('div');
modeLabel.textContent = '随机颜色模式:';
modeLabel.style.color = '#e1e1e1';
modeLabel.style.fontSize = '14px';
modeLabel.style.fontWeight = '500';
const modeSelect = document.createElement('select');
modeSelect.style.width = '100%';
modeSelect.style.padding = '6px 10px';
modeSelect.style.backgroundColor = 'rgba(45, 45, 55, 0.8)';
modeSelect.style.color = '#e1e1e1';
modeSelect.style.border = '1px solid rgba(255, 255, 255, 0.2)';
modeSelect.style.borderRadius = '6px';
modeSelect.style.outline = 'none';
// 添加选项
const options = [
const { modeContainer, modeLabel } = createModeContainer('随机颜色模式:');
const modeSelect = createSelectBox();
addOptionsToSelect(modeSelect, [
{ value: '完全随机', text: '完全随机(每个节点不同颜色)' },
{ value: '统一随机', text: '统一随机(所有节点相同颜色)' }
];
]);
options.forEach(option => {
const opt = document.createElement('option');
opt.value = option.value;
opt.textContent = option.text;
// 设置默认选中项
Array.from(modeSelect.options).forEach(option => {
if (option.value === this.colorMode) {
opt.selected = true;
option.selected = true;
}
modeSelect.appendChild(opt);
});
// 监听选择变化
modeSelect.addEventListener('change', (e) => {
this.colorMode = e.target.value;
}); modeContainer.appendChild(modeLabel);
});
modeContainer.appendChild(modeSelect);
// 添加按钮到容器
@@ -312,6 +271,42 @@ export class LayoutPanel { constructor() {
this.container.appendChild(modeContainer);
this.container.appendChild(syncModeContainer);
// 添加面板调整大小时重新调整按钮文字大小的功能
const resizeObserver = new ResizeObserver(() => {
if (this.buttonsContainer) {
const buttons = this.buttonsContainer.querySelectorAll('.layout-btn');
buttons.forEach(button => {
// 检查是否有adjustTextFontSize函数
if (typeof adjustTextFontSize === 'function') {
adjustTextFontSize(button);
} else if (window.adjustTextFontSize) {
window.adjustTextFontSize(button);
}
});
}
});
// 观察容器大小变化
resizeObserver.observe(this.container);
// 调整按钮布局
adjustButtonsLayout(this.buttonsContainer);
// 强制触发一次字体调整,确保初始状态正确
setTimeout(() => {
const buttons = this.buttonsContainer.querySelectorAll('.layout-btn');
buttons.forEach(button => {
if (typeof adjustTextFontSize === 'function') {
adjustTextFontSize(button);
} else if (window.adjustTextFontSize) {
window.adjustTextFontSize(button);
} else {
// 如果没有找到调整函数,则确保基本字体大小
button.style.fontSize = '15px';
}
});
}, 200);
// 使用外部样式模块注入样式
injectStyles('layout-panel-styles', layoutPanelStyles);
@@ -320,152 +315,57 @@ export class LayoutPanel { constructor() {
e.stopPropagation();
});
// 添加可拖动区域(左上角)
const dragHandle = document.createElement('div');
dragHandle.style.position = 'absolute';
dragHandle.style.left = '0';
dragHandle.style.top = '0';
dragHandle.style.width = '32px';
dragHandle.style.height = '32px';
dragHandle.style.cursor = 'move';
dragHandle.style.borderTopLeftRadius = '16px';
dragHandle.style.background = 'linear-gradient(135deg, rgba(255,255,255,0.08) 60%, transparent 100%)';
dragHandle.style.zIndex = '100001';
dragHandle.title = '拖动面板';
this.container.appendChild(dragHandle);
this.container.style.userSelect = 'none';
// 拖动逻辑
let isDragging = false;
let dragOffsetX = 0;
let dragOffsetY = 0;
dragHandle.addEventListener('mousedown', (e) => {
e.stopPropagation();
isDragging = true;
// 记录鼠标与面板左上角的偏移
const rect = this.container.getBoundingClientRect();
dragOffsetX = e.clientX - rect.left;
dragOffsetY = e.clientY - rect.top;
document.body.style.cursor = 'move';
});
document.addEventListener('mousemove', (e) => {
if (isDragging) {
// 计算新位置
const left = e.clientX - dragOffsetX;
const top = e.clientY - dragOffsetY;
this.container.style.left = `${left + 0.5}px`;
this.container.style.top = `${top + 0.5}px`;
this.container.style.transform = 'none';
}
});
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
document.body.style.cursor = '';
}
});
// 添加到文档
document.body.appendChild(this.container);
}
// 创建按钮辅助方法 - 更美观的按钮
_createButton(text, type, bgColor) {
const button = document.createElement('button');
button.className = `layout-btn layout-btn-${type}`;
button.textContent = text;
button.title = text;
// 添加更现代的按钮样式
const isPrimary = type === 'pick';
const isRainbow = type === 'rainbow';
// 基础样式
Object.assign(button.style, {
backgroundColor: bgColor || (isRainbow ? 'rgba(138, 43, 226, 0.75)' : 'rgba(16, 130, 246, 0.75)'),
border: 'none',
borderRadius: '10px',
padding: '10px 16px',
color: '#ffffff',
fontSize: '14px',
fontWeight: '600',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
cursor: 'pointer',
transition: 'all 0.2s ease',
textShadow: '0 1px 2px rgba(0, 0, 0, 0.2)',
minWidth: '120px',
textAlign: 'center',
position: 'relative',
overflow: 'hidden',
margin: '2px'
});
// 添加悬停效果
button.addEventListener('mouseover', () => {
const baseColor = bgColor || (isRainbow ? 'rgba(138, 43, 226, 0.75)' : 'rgba(16, 130, 246, 0.75)');
// 提取RGB部分,增加不透明度
const colorPattern = /rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/;
const match = baseColor.match(colorPattern);
if (match) {
const r = match[1];
const g = match[2];
const b = match[3];
button.style.backgroundColor = `rgba(${r}, ${g}, ${b}, 0.9)`;
} else {
button.style.backgroundColor = baseColor;
// 应用当前主题或默认主题
setTimeout(() => {
let themeId = null;
try {
if (window.app?.extensionManager?.setting) {
themeId = window.app.extensionManager.setting.get("LayoutPanel.theme");
}
} catch(e) {
console.warn("获取主题设置失败:", e);
}
button.style.boxShadow = '0 6px 16px rgba(0, 0, 0, 0.25)';
button.style.transform = 'translateY(-2px)';
});
// 添加离开效果
button.addEventListener('mouseout', () => {
button.style.backgroundColor = bgColor || (isRainbow ? 'rgba(138, 43, 226, 0.75)' : 'rgba(16, 130, 246, 0.75)');
button.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
button.style.transform = 'translateY(0)';
});
// 添加点击效果
button.addEventListener('mousedown', () => {
button.style.transform = 'translateY(1px)';
button.style.boxShadow = '0 2px 6px rgba(0, 0, 0, 0.15)';
});
button.addEventListener('mouseup', () => {
button.style.transform = 'translateY(0)';
button.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
});
return button;
if (!themeId) {
themeId = getDefaultTheme();
}
if (themeId) {
this.setTheme(themeId);
// 最后应用透明度,确保不被主题覆盖
setTimeout(() => {
this.container.dataset.initialSetup = 'completed';
this.setOpacity(opacity);
this.setButtonOpacity(buttonOpacity);
}, 150);
}
}, 100);
}
// 此方法已被移除,使用_showAtMousePosition替代
// 设置随机节点颜色
// 设置随机节点颜色
_setRandomNodeColor() {
try {
// 获取ComfyUI应用实例
const app = this._getComfyUIApp();
const app = getComfyUIApp();
if (!app) {
this._showNotification("无法获取ComfyUI应用实例");
showNotification("无法获取ComfyUI应用实例");
return;
}
// 获取选中的节点和组
const selectedNodes = this._getSelectedNodes(app);
const selectedGroups = this._getSelectedGroups(app);
const selectedNodes = getSelectedNodes(app);
const selectedGroups = getSelectedGroups(app);
if (selectedNodes.length === 0 && selectedGroups.length === 0) {
this._showNotification("请先选择要应用颜色的节点或组");
showNotification("请先选择要应用颜色的节点或组");
return;
}
// 生成随机颜色函数
const getRandomColor = () => {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};
// 根据颜色模式应用颜色
// 根据颜色模式应用颜色
if (this.colorMode === '统一随机') {
// 统一随机模式 - 所有节点和组使用同一个随机颜色
const uniformColor = getRandomColor();
@@ -478,7 +378,7 @@ export class LayoutPanel { constructor() {
group.color = uniformColor;
});
this._showNotification("已应用统一随机颜色", "info");
showNotification("已应用统一随机颜色", "info");
} else {
// 完全随机模式 - 每个节点和组使用不同的随机颜色
selectedNodes.forEach(node => {
@@ -490,13 +390,12 @@ export class LayoutPanel { constructor() {
group.color = getRandomColor();
});
this._showNotification("已应用完全随机颜色", "info");
showNotification("已应用完全随机颜色", "info");
}
app.graph.setDirtyCanvas(true, true);
// 不再自动关闭面板,面板只通过快捷键 toggle() 控制显示/隐藏
} catch (error) {
console.error("设置随机颜色失败:", error);
this._showNotification(`设置随机颜色失败: ${error.message}`);
showNotification(`设置随机颜色失败: ${error.message}`);
}
}
@@ -504,22 +403,21 @@ export class LayoutPanel { constructor() {
_openCustomColorPicker() {
try {
// 获取ComfyUI应用实例
const app = this._getComfyUIApp();
const app = getComfyUIApp();
if (!app) {
this._showNotification("无法获取ComfyUI应用实例");
showNotification("无法获取ComfyUI应用实例");
return;
}
// 获取选中的节点和组
const selectedNodes = this._getSelectedNodes(app);
const selectedGroups = this._getSelectedGroups(app);
const selectedNodes = getSelectedNodes(app);
const selectedGroups = getSelectedGroups(app);
if (selectedNodes.length === 0 && selectedGroups.length === 0) {
this._showNotification("请先选择要应用颜色的节点或组");
showNotification("请先选择要应用颜色的节点或组");
return;
}
// 不再自动关闭面板,面板只通过快捷键 toggle() 控制显示/隐藏
// 创建颜色选择器
setTimeout(() => {
const defaultColor = selectedNodes.length > 0 ?
@@ -533,7 +431,7 @@ export class LayoutPanel { constructor() {
colorInput.style.visibility = 'hidden';
document.body.appendChild(colorInput);
// 处理颜色变化 -
// 处理颜色变化
colorInput.addEventListener('input', (e) => {
const color = e.target.value;
@@ -547,234 +445,60 @@ export class LayoutPanel { constructor() {
});
app.graph.setDirtyCanvas(true, true);
}); colorInput.addEventListener('change', (e) => { this._showNotification("已应用自定义颜色", "info");
});
colorInput.addEventListener('change', (e) => {
showNotification("已应用自定义颜色", "info");
document.body.removeChild(colorInput);
});
// 模拟点击打开颜色选择器
colorInput.click();
}, 100);
} catch (error) {
console.error("打开颜色选择器失败:", error);
this._showNotification(`打开颜色选择器失败: ${error.message}`);
showNotification(`打开颜色选择器失败: ${error.message}`);
}
}
// 获取ComfyUI应用实例
_getComfyUIApp() {
if (window.app?.canvas && window.app?.graph) {
return window.app;
}
if (window.LiteGraph?.LGraphCanvas?.active_canvas) {
const canvas = LiteGraph.LGraphCanvas.active_canvas;
if (canvas?.graph) {
return { canvas, graph: canvas.graph };
}
}
const canvasElement = document.querySelector(".litegraph.litegraph-canvas");
if (canvasElement?.lgraphcanvas) {
const canvas = canvasElement.lgraphcanvas;
if (canvas?.graph) {
return { canvas, graph: canvas.graph };
}
}
return null;
}
// 获取选中的节点
_getSelectedNodes(app) {
if (app.canvas.selected_nodes?.length) {
return Array.from(app.canvas.selected_nodes);
}
const selectedNodes = [];
if (app.graph?._nodes) {
for (const node of app.graph._nodes) {
if (node.is_selected) {
selectedNodes.push(node);
}
}
}
return selectedNodes;
}
// 获取选中的组
_getSelectedGroups(app) {
const selectedGroups = [];
if (app.canvas?.selected_groups?.length) {
return Array.from(app.canvas.selected_groups);
}
if (app.graph?.groups) {
for (const group of app.graph.groups) {
if (group.selected) {
selectedGroups.push(group);
}
}
}
return selectedGroups;
}
// 显示通知信息
_showNotification(message, type = 'error') {
const notification = document.createElement('div');
notification.className = 'layout-notification';
notification.textContent = message;
// 通知基本样式
const baseNotificationStyles = {
position: 'fixed',
top: '24px',
left: '50%',
transform: 'translateX(-50%) translateY(-20px)',
padding: '12px 24px',
borderRadius: '8px',
fontSize: '14px',
fontWeight: '500',
zIndex: '10000',
transition: 'all 0.3s ease',
backdropFilter: 'blur(8px)',
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.2)',
border: '1px solid rgba(255, 255, 255, 0.1)',
width: 'auto',
minWidth: '240px',
maxWidth: '90%',
textAlign: 'center',
opacity: '0'
};
// 应用样式
Object.assign(notification.style, baseNotificationStyles);
if (type === 'info') {
notification.style.backgroundColor = 'rgba(20, 120, 60, 0.85)';
notification.style.color = 'white';
} else if (type === 'warn') {
notification.style.backgroundColor = 'rgba(230, 150, 10, 0.85)';
notification.style.color = 'white';
} else {
notification.style.backgroundColor = 'rgba(200, 0, 0, 0.85)';
notification.style.color = 'white';
}
document.body.appendChild(notification);
// 显示动画 - 从上方移入
setTimeout(() => {
notification.style.opacity = '1';
notification.style.transform = 'translateX(-50%) translateY(0)';
}, 10);
setTimeout(() => {
notification.style.opacity = '0';
notification.style.transform = 'translateX(-50%) translateY(-20px)'; // 向上方消失
setTimeout(() => {
if (notification.parentNode) {
document.body.removeChild(notification);
}
}, 300);
}, 3000);
}
// 辅助函数:判断节点排列主方向
_getMainDirection(centers) {
let dx = 0, dy = 0;
for (let i = 1; i < centers.length; i++) {
dx += Math.abs(centers[i].x - centers[0].x);
dy += Math.abs(centers[i].y - centers[0].y);
}
return dx > dy ? 'horizontal' : 'vertical';
}
// 节点同步逻辑
_syncSelectedNodes(mode) {
try {
const app = this._getComfyUIApp();
const app = getComfyUIApp();
if (!app) {
this._showNotification('无法获取ComfyUI应用实例');
showNotification('无法获取ComfyUI应用实例');
return;
}
const selectedNodes = this._getSelectedNodes(app);
const selectedNodes = getSelectedNodes(app);
if (selectedNodes.length < 2) {
this._showNotification('请至少选择两个节点进行同步');
showNotification('请至少选择两个节点进行同步');
return;
}
let result;
if (mode === 'size') {
// 大小同步:统一为最大宽高
let maxW = 0, maxH = 0;
selectedNodes.forEach(node => {
if (node.size) {
maxW = Math.max(maxW, node.size[0]);
maxH = Math.max(maxH, node.size[1]);
}
});
selectedNodes.forEach(node => {
if (node.size) {
node.size[0] = maxW;
node.size[1] = maxH;
}
});
this._showNotification('已同步为统一大小', 'info');
result = syncNodeSize(selectedNodes);
showNotification('已同步为统一大小', 'info');
} else if (mode === 'wh') {
// 宽高同步:判断排列方向
const centers = selectedNodes.map(node => ({
node,
x: node.pos ? node.pos[0] + (node.size ? node.size[0] / 2 : 0) : 0,
y: node.pos ? node.pos[1] + (node.size ? node.size[1] / 2 : 0) : 0
}));
const mainDir = this._getMainDirection(centers);
if (mainDir === 'horizontal') {
// 横向排列,同步高度
let maxH = 0;
selectedNodes.forEach(node => {
if (node.size) maxH = Math.max(maxH, node.size[1]);
});
selectedNodes.forEach(node => {
if (node.size) node.size[1] = maxH;
});
this._showNotification('已同步高度(横向排列)', 'info');
} else {
// 纵向排列,同步宽度
let maxW = 0;
selectedNodes.forEach(node => {
if (node.size) maxW = Math.max(maxW, node.size[0]);
});
selectedNodes.forEach(node => {
if (node.size) node.size[0] = maxW;
});
this._showNotification('已同步宽度(竖向排列)', 'info');
}
result = syncNodeWidthHeight(selectedNodes);
const direction = result.direction === 'horizontal' ? '横向排列' : '纵向排列';
const dimension = result.direction === 'horizontal' ? '高度' : '宽度';
showNotification(`已同步${dimension}(${direction})`, 'info');
} else if (mode === 'align') {
// 同步对齐:自动排列节点
const centers = selectedNodes.map(node => ({
node,
x: node.pos ? node.pos[0] + (node.size ? node.size[0] / 2 : 0) : 0,
y: node.pos ? node.pos[1] + (node.size ? node.size[1] / 2 : 0) : 0
}));
const mainDir = this._getMainDirection(centers);
if (mainDir === 'horizontal') {
// 横向排列,对齐Y轴
const avgY = centers.reduce((sum, c) => sum + c.y, 0) / centers.length;
selectedNodes.forEach(node => {
if (node.pos) node.pos[1] = avgY - (node.size ? node.size[1] / 2 : 0);
});
this._showNotification('已横向对齐(Y轴对齐)', 'info');
} else {
// 纵向排列,对齐X轴
const avgX = centers.reduce((sum, c) => sum + c.x, 0) / centers.length;
selectedNodes.forEach(node => {
if (node.pos) node.pos[0] = avgX - (node.size ? node.size[0] / 2 : 0);
});
this._showNotification('已纵向对齐(X轴对齐)', 'info');
}
result = alignNodes(selectedNodes);
const alignment = result.direction === 'horizontal' ? 'Y轴对齐' : 'X轴对齐';
const direction = result.direction === 'horizontal' ? '横向' : '纵向';
showNotification(`已${direction}对齐(${alignment})`, 'info');
}
app.graph.setDirtyCanvas(true, true);
} catch (e) {
this._showNotification('节点同步失败: ' + e.message);
showNotification('节点同步失败: ' + e.message);
}
}
}
@@ -783,5 +507,6 @@ export const DEFAULT_CONFIG = {
enabled: true, // 默认启用
shortcut: 'alt+x',
colorMode: '完全随机', // 默认颜色模式
continueIteration: false // 默认应用颜色后关闭面板
continueIteration: false, // 默认应用颜色后关闭面板
theme: '古神之眼' // 默认主题
};
+143 -23
View File
@@ -1,11 +1,24 @@
// 智能布局扩展
import { app } from "/scripts/app.js";
import { LayoutPanel, DEFAULT_CONFIG } from "./LayoutCore.js";
import { registerTheme, getRegisteredThemes, setDefaultTheme, initDefaultThemes } from "./styles/UIStyles.js";
// 初始化默认主题
try {
// 调用主题初始化函数
if (initDefaultThemes()) {
console.log("主题系统已成功初始化");
} else {
console.warn("主题系统初始化返回失败");
}
} catch (error) {
console.error("主题系统初始化失败:", error);
}
// 确保全局唯一实例
function getOrCreateLayoutInstance() {
if (!app.canvas) {
console.warn("智能布局:app.canvas未初始化");
console.warn("界面布局:app.canvas未初始化");
return null;
}
@@ -58,23 +71,64 @@ export const layoutExt = {
this.initLayoutPanel();
},
initLayoutPanel() {
initLayoutPanel() {
// 初始化布局面板
const layoutPanel = getOrCreateLayoutInstance();
if (!layoutPanel) return;
// 应用初始设置 - 功能始终启用
// 应用所有初始设置 - 确保所有参数都在初始化时应用
const applySettings = () => {
// 默认始终启用,不再依赖设置
layoutPanel.setEnabled(true);
const shortcut = app.extensionManager.setting.get("LayoutPanel.shortcut", DEFAULT_CONFIG.shortcut);
layoutPanel.setShortcut(shortcut);
try {
// 主要设置
const shortcut = app.extensionManager?.setting?.get("LayoutPanel.shortcut") ?? DEFAULT_CONFIG.shortcut;
const theme = app.extensionManager?.setting?.get("LayoutPanel.theme") ?? DEFAULT_CONFIG.theme;
// 透明度设置
const opacity = app.extensionManager?.setting?.get("LayoutPanel.opacity") ?? 85;
const buttonOpacity = app.extensionManager?.setting?.get("LayoutPanel.buttonOpacity") ?? 90;
// 启用面板(始终启用)
layoutPanel.setEnabled(true);
// 应用所有设置 - 顺序很重要
layoutPanel.setShortcut(shortcut);
layoutPanel.setTheme(theme);
// 确保透明度设置最后应用,这样不会被主题初始化覆盖
setTimeout(() => {
layoutPanel.setOpacity(opacity);
layoutPanel.setButtonOpacity(buttonOpacity);
}, 100);
console.log("布局面板初始设置已加载:", { shortcut, theme, opacity, buttonOpacity });
} catch (e) {
console.warn("应用设置时出错:", e);
// 应用默认设置作为回退
layoutPanel.setShortcut(DEFAULT_CONFIG.shortcut);
layoutPanel.setTheme(DEFAULT_CONFIG.theme);
layoutPanel.setOpacity(85);
layoutPanel.setButtonOpacity(90);
}
};
applySettings();
}
};
// 获取所有注册主题用于设置选择
function getThemeOptions() {
try {
const themes = getRegisteredThemes();
return themes.map(theme => ({
text: theme.name,
value: theme.id
}));
} catch (e) {
console.error("获取主题选项失败:", e);
return [{ text: '古神之眼', value: '古神之眼' }];
}
}
// 注册设置面板
app.registerExtension({
name: "ComfyUI.LayoutPanel.Settings",
@@ -88,17 +142,45 @@ app.registerExtension({
// 初始化布局面板
const layoutPanel = getOrCreateLayoutInstance();
if (!layoutPanel) return;
// 始终启用布局功能,不再依赖设置 layoutPanel.setEnabled(true);
layoutPanel.setShortcut(app.extensionManager.setting.get("LayoutPanel.shortcut") ?? DEFAULT_CONFIG.shortcut);
}, settings: [
// 确保显式加载和应用所有设置,与init方法保持一致
try {
// 先启用面板
layoutPanel.setEnabled(true);
// 然后应用所有保存的设置
const shortcut = app.extensionManager?.setting?.get("LayoutPanel.shortcut") ?? DEFAULT_CONFIG.shortcut;
const theme = app.extensionManager?.setting?.get("LayoutPanel.theme") ?? DEFAULT_CONFIG.theme;
const opacity = app.extensionManager?.setting?.get("LayoutPanel.opacity") ?? 85;
const buttonOpacity = app.extensionManager?.setting?.get("LayoutPanel.buttonOpacity") ?? 90;
// 应用设置 - 注意顺序
layoutPanel.setShortcut(shortcut);
layoutPanel.setTheme(theme);
// 确保透明度在主题后设置
setTimeout(() => {
layoutPanel.setOpacity(opacity);
layoutPanel.setButtonOpacity(buttonOpacity);
}, 100);
} catch (e) {
console.warn("应用设置时出错:", e);
// 应用默认设置
layoutPanel.setShortcut(DEFAULT_CONFIG.shortcut);
layoutPanel.setTheme(DEFAULT_CONFIG.theme);
layoutPanel.setOpacity(85);
layoutPanel.setButtonOpacity(90);
}
},
settings: [
// 移除启用/禁用设置选项
{
id: "LayoutPanel.shortcut",
name: "快捷键",
name: "快捷呼出",
type: "text",
defaultValue: DEFAULT_CONFIG.shortcut,
tooltip: "弹出节点颜色工具面板的快捷键(如alt+x)",
category: ["🍺界面布局", "1·功能", "快捷键"],
tooltip: "弹出界面布局工具面板的快捷键(如alt+x)",
category: ["🍺界面布局", "1·功能", "快捷呼出"],
onChange(value) {
if (typeof value === 'string' && value.includes('+')) {
const layoutPanel = getOrCreateLayoutInstance();
@@ -110,31 +192,69 @@ app.registerExtension({
},
{
id: "LayoutPanel.opacity",
name: "界面透明度",
name: "背景透明",
type: "slider",
defaultValue: 85,
attrs: { min: 0, max: 100, step: 1 },
tooltip: "设置智能布局主面板的透明度(0~100%)",
category: ["🍺界面布局", "2·外观", "界面透明度"],
tooltip: "设置界面布局主面板背景的透明度(0~100%)",
category: ["🍺界面布局", "2·外观", "背景透明"],
onChange(value) {
const layoutPanel = getOrCreateLayoutInstance();
if (layoutPanel && typeof layoutPanel.setOpacity === 'function') {
layoutPanel.setOpacity(value);
}
}
},
{
id: "LayoutPanel.buttonOpacity",
name: "按钮透明",
type: "slider",
defaultValue: 90,
attrs: { min: 0, max: 100, step: 1 },
tooltip: "设置界面布局面板中按钮的透明度(0~100%)",
category: ["🍺界面布局", "2·外观", "按钮透明"],
onChange(value) {
const layoutPanel = getOrCreateLayoutInstance();
if (layoutPanel && typeof layoutPanel.setButtonOpacity === 'function') {
layoutPanel.setButtonOpacity(value);
}
}
},
{
id: "LayoutPanel.theme",
name: "界面主题",
type: "combo",
defaultValue: DEFAULT_CONFIG.theme,
tooltip: "选择界面布局面板的视觉主题",
options: getThemeOptions,
category: ["🍺界面布局", "2·外观", "界面主题"],
onChange(value) {
const layoutPanel = getOrCreateLayoutInstance();
if (layoutPanel && typeof layoutPanel.setTheme === 'function') {
layoutPanel.setTheme(value);
}
}
}
]
});
// 主扩展注册
app.registerExtension({
name: "ComfyUI.LayoutPanel",
init() {
setTimeout(() => {
if (app.canvas) {
layoutExt.init();
} else {
console.error("智能布局:延迟初始化失败,app.canvas仍未准备好");
}
}, 500);
try {
console.log("正在初始化智能布局面板...");
// 延迟初始化,确保其他扩展已经加载
setTimeout(() => {
if (app.canvas) {
layoutExt.init();
console.log("智能布局面板初始化完成");
} else {
console.error("智能布局:延迟初始化失败,app.canvas仍未准备好");
}
}, 1000);
} catch (e) {
console.error("初始化智能布局面板时出错:", e);
}
}
});
+845
View File
@@ -0,0 +1,845 @@
// 智能布局 UI 样式统一模块
// ==========================================================
// 主题管理相关功能
// ==========================================================
// 导入主题模块
import AncientGodTheme from "./古神之眼/AncientGodTheme.js";
// 存储所有已注册的主题
const registeredThemes = {};
let currentTheme = null;
let defaultTheme = '古神之眼';
// 自动注册默认主题
export function initDefaultThemes() { try {
// 注册主题
registerTheme('古神之眼', AncientGodTheme);
setDefaultTheme('古神之眼');
console.log("默认主题已成功注册");
return true;
} catch (error) {
console.error("注册默认主题失败:", error);
return false;
}
}
// 注册主题 - 主题必须实现特定接口
export function registerTheme(id, themeModule) {
if (!id || typeof id !== 'string') {
console.error('主题ID必须是有效字符串');
return false;
}
// 验证主题模块是否符合接口规范
if (!themeModule || typeof themeModule !== 'object') {
console.error(`主题 "${id}" 必须是有效对象`);
return false;
}
// 验证必要的方法
const requiredMethods = ['init', 'applyTheme', 'flipCoin', 'cleanup'];
for (const method of requiredMethods) {
if (typeof themeModule[method] !== 'function') {
console.error(`主题 "${id}" 缺少必要方法: ${method}`);
return false;
}
}
// 验证必要的属性
if (!themeModule.name) {
console.error(`主题 "${id}" 缺少名称属性`);
return false;
}
// 注册主题
registeredThemes[id] = themeModule;
console.log(`主题 "${id}" (${themeModule.name}) 已成功注册`);
return true;
}
// 获取已注册的所有主题
export function getRegisteredThemes() {
const themes = [];
for (const id in registeredThemes) {
themes.push({
id: id,
name: registeredThemes[id].name || id
});
}
return themes;
}
// 获取主题模块
export function getTheme(id) {
return registeredThemes[id] || null;
}
// 应用主题
export function applyTheme(id, container, coinElement) {
// 清理当前主题(如果有)
if (currentTheme && registeredThemes[currentTheme]) {
registeredThemes[currentTheme].cleanup();
}
// 获取并应用新主题
const themeModule = registeredThemes[id];
if (!themeModule) {
console.error(`主题 "${id}" 未注册`);
return false;
}
// 初始化并应用新主题
// 确保硬币元素存在且为初始状态
if (coinElement) {
// 移除可能的 flipped 类,确保硬币始终从正面开始
if (coinElement.classList.contains('flipped')) {
coinElement.classList.remove('flipped');
}
}
// 强制重新创建背景和硬币
themeModule.init(container, coinElement);
themeModule.applyTheme();
currentTheme = id;
return true;
}
// 设置默认主题
export function setDefaultTheme(id) {
if (registeredThemes[id]) {
defaultTheme = id;
return true;
}
return false;
}
// 获取默认主题
export function getDefaultTheme() {
return defaultTheme;
}
// ==========================================================
// 通用工具函数
// ==========================================================
// 工具函数:向页面注入样式
export function injectStyles(styleId, styleContent) {
// 如果已经存在相同ID的样式,则不重复注入
if (document.getElementById(styleId)) return;
const styleElement = document.createElement('style');
styleElement.id = styleId;
styleElement.textContent = styleContent;
document.head.appendChild(styleElement);
}
// 工具函数:移除已注入的样式
export function removeStyles(styleId) {
const styleElement = document.getElementById(styleId);
if (styleElement && styleElement.parentNode) {
styleElement.parentNode.removeChild(styleElement);
}
}
// ==========================================================
// CSS 样式定义 - 简化为最基本的结构,其余移至主题实现
// ==========================================================
// 布局面板相关样式 - 只保留必要的基础结构
export const layoutPanelStyles = `
.layout-panel {
position: fixed;
z-index: 99999;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
user-select: none;
transition: opacity 0.3s;
border-radius: 16px;
}
`;
// ==========================================================
// 面板相关函数
// ==========================================================
// 创建面板UI元素
export function createPanelElement(mousePosition) {
// 创建容器
const container = document.createElement('div');
container.className = 'layout-panel';
// 初始位置,后续会根据面板实际大小进行调整
container.style.left = `${mousePosition.x}px`;
container.style.top = `${mousePosition.y}px`;
container.style.transform = 'none';
container.style.display = 'none';
container.style.pointerEvents = 'none';
container.style.opacity = '0';
container.style.padding = '30px'; // 增加内边距适应大尺寸硬币
container.style.width = '360px'; // 设置基础宽度
container.style.boxSizing = 'border-box'; // 确保padding不增加总宽度
// 添加拖动区域 - 改为在面板边缘创建多个拖动区域
addDragHandles(container);
// 注入基础样式
injectStyles('layout-panel-styles', layoutPanelStyles);
return container;
}
// 添加多个拖动区域到面板边缘
function addDragHandles(container) {
// 创建拖动状态对象 - 所有拖动句柄共享
const dragState = {
isDragging: false,
offsetX: 0,
offsetY: 0
};
// 拖动处理函数 - 所有拖动区域共享
const handleMouseMove = function(e) {
if (!dragState.isDragging) return;
// 计算新位置
const left = e.clientX - dragState.offsetX;
const top = e.clientY - dragState.offsetY;
// 使用 requestAnimationFrame 减少重绘次数
requestAnimationFrame(() => {
container.style.left = `${left}px`;
container.style.top = `${top}px`;
// 一旦开始拖动,就固定不使用 transform,避免布局重新计算
if (container.style.transform !== 'none') {
container.style.transform = 'none';
}
});
};
const handleMouseUp = function() {
if (!dragState.isDragging) return;
dragState.isDragging = false;
document.body.style.cursor = '';
// 重要:在拖动结束后移除临时添加的事件监听器
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
// 创建拖动开始函数
const createDragStart = (handle) => {
return (e) => {
e.stopPropagation();
e.preventDefault();
// 记录鼠标与面板左上角的偏移
const rect = container.getBoundingClientRect();
dragState.offsetX = e.clientX - rect.left;
dragState.offsetY = e.clientY - rect.top;
dragState.isDragging = true;
document.body.style.cursor = 'move';
// 只在开始拖动时添加事件监听器
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
};
};
// 定义边缘宽度
const edgeWidth = 15; // 拖动区域宽度,px
// 创建顶部拖动区域
const topHandle = document.createElement('div');
topHandle.className = 'layout-drag-handle layout-drag-handle-top';
Object.assign(topHandle.style, {
position: 'absolute',
top: '0',
left: edgeWidth + 'px',
right: edgeWidth + 'px',
height: edgeWidth + 'px',
cursor: 'move',
zIndex: '10001'
});
topHandle.addEventListener('mousedown', createDragStart(topHandle));
container.appendChild(topHandle);
// 创建底部拖动区域
const bottomHandle = document.createElement('div');
bottomHandle.className = 'layout-drag-handle layout-drag-handle-bottom';
Object.assign(bottomHandle.style, {
position: 'absolute',
bottom: '0',
left: edgeWidth + 'px',
right: edgeWidth + 'px',
height: edgeWidth + 'px',
cursor: 'move',
zIndex: '10001'
});
bottomHandle.addEventListener('mousedown', createDragStart(bottomHandle));
container.appendChild(bottomHandle);
// 创建左侧拖动区域
const leftHandle = document.createElement('div');
leftHandle.className = 'layout-drag-handle layout-drag-handle-left';
Object.assign(leftHandle.style, {
position: 'absolute',
top: edgeWidth + 'px',
bottom: edgeWidth + 'px',
left: '0',
width: edgeWidth + 'px',
cursor: 'move',
zIndex: '10001'
});
leftHandle.addEventListener('mousedown', createDragStart(leftHandle));
container.appendChild(leftHandle);
// 创建右侧拖动区域
const rightHandle = document.createElement('div');
rightHandle.className = 'layout-drag-handle layout-drag-handle-right';
Object.assign(rightHandle.style, {
position: 'absolute',
top: edgeWidth + 'px',
bottom: edgeWidth + 'px',
right: '0',
width: edgeWidth + 'px',
cursor: 'move',
zIndex: '10001'
});
rightHandle.addEventListener('mousedown', createDragStart(rightHandle));
container.appendChild(rightHandle);
// 创建四个角落的拖动区域
const topLeftHandle = document.createElement('div');
topLeftHandle.className = 'layout-drag-handle layout-drag-handle-top-left';
Object.assign(topLeftHandle.style, {
position: 'absolute',
top: '0',
left: '0',
width: edgeWidth + 'px',
height: edgeWidth + 'px',
cursor: 'move',
zIndex: '10001',
borderTopLeftRadius: '16px'
});
topLeftHandle.addEventListener('mousedown', createDragStart(topLeftHandle));
container.appendChild(topLeftHandle);
const topRightHandle = document.createElement('div');
topRightHandle.className = 'layout-drag-handle layout-drag-handle-top-right';
Object.assign(topRightHandle.style, {
position: 'absolute',
top: '0',
right: '0',
width: edgeWidth + 'px',
height: edgeWidth + 'px',
cursor: 'move',
zIndex: '10001',
borderTopRightRadius: '16px'
});
topRightHandle.addEventListener('mousedown', createDragStart(topRightHandle));
container.appendChild(topRightHandle);
const bottomLeftHandle = document.createElement('div');
bottomLeftHandle.className = 'layout-drag-handle layout-drag-handle-bottom-left';
Object.assign(bottomLeftHandle.style, {
position: 'absolute',
bottom: '0',
left: '0',
width: edgeWidth + 'px',
height: edgeWidth + 'px',
cursor: 'move',
zIndex: '10001',
borderBottomLeftRadius: '16px'
});
bottomLeftHandle.addEventListener('mousedown', createDragStart(bottomLeftHandle));
container.appendChild(bottomLeftHandle);
const bottomRightHandle = document.createElement('div');
bottomRightHandle.className = 'layout-drag-handle layout-drag-handle-bottom-right';
Object.assign(bottomRightHandle.style, {
position: 'absolute',
bottom: '0',
right: '0',
width: edgeWidth + 'px',
height: edgeWidth + 'px',
cursor: 'move',
zIndex: '10001',
borderBottomRightRadius: '16px'
});
bottomRightHandle.addEventListener('mousedown', createDragStart(bottomRightHandle));
container.appendChild(bottomRightHandle);
// 为调试可视化边框 - 通过注释控制是否添加
// injectStyles('drag-handles-debug', `
// .layout-drag-handle {
// border: 1px dashed rgba(255, 255, 255, 0.15);
// background-color: rgba(128, 128, 255, 0.05);
// }
// `);
}
// ==========================================================
// UI 控件相关函数
// ==========================================================
// 创建按钮容器
export function createButtonsContainer() {
const buttonsContainer = document.createElement('div');
buttonsContainer.className = 'layout-buttons-container';
buttonsContainer.style.display = 'flex';
buttonsContainer.style.flexWrap = 'wrap'; // 允许按钮在需要时换行
buttonsContainer.style.justifyContent = 'center'; // 居中排列
buttonsContainer.style.width = '100%';
buttonsContainer.style.gap = '8px'; // 按钮之间的间距
return buttonsContainer;
}
// 创建按钮
export function createButton(text, type, bgColor) {
const button = document.createElement('button');
button.className = `layout-btn layout-btn-${type}`;
button.textContent = text;
button.title = text;
// 自适应布局的按钮样式
button.style.padding = '8px 12px';
button.style.flexGrow = '1'; // 按钮可以增长
button.style.flexBasis = '0'; // 所有按钮基础宽度相同
button.style.minWidth = '80px'; // 最小宽度
button.style.maxWidth = 'none'; // 移除最大宽度限制,让按钮自动适应
button.style.textAlign = 'center'; // 确保文本居中
button.style.justifyContent = 'center'; // Flex布局居中
button.style.alignItems = 'center'; // Flex布局居中
button.style.display = 'flex'; // 改为flex布局以确保内容居中
button.style.cursor = 'pointer';
button.style.borderRadius = '10px';
button.style.transition = 'all 0.2s ease';
button.style.margin = '1px';
button.style.whiteSpace = 'nowrap'; // 防止文字换行
button.style.overflow = 'hidden';
// 使用自定义属性存储原始文本和字体大小,便于调整显示
button.dataset.originalText = text;
button.dataset.originalFontSize = '15px'; // 调大默认字体大小为15px(原为14px)
button.style.fontSize = button.dataset.originalFontSize;
// 添加点击效果
button.addEventListener('mousedown', () => {
button.style.transform = 'translateY(1px)';
});
button.addEventListener('mouseup', () => {
button.style.transform = 'translateY(0)';
});
// 添加自适应字体大小功能
button.addEventListener('DOMNodeInserted', () => {
setTimeout(() => adjustTextFontSize(button), 0);
});
return button;
}
// 新增函数:调整文字字体大小以适应按钮宽度
function adjustTextFontSize(button) {
if (!button || !button.dataset.originalText) return;
// 重置为原始字体大小以便测量
button.style.fontSize = button.dataset.originalFontSize || '15px';
// 计算按钮可用宽度(考虑内边距)
const buttonWidth = button.clientWidth;
const paddingLeft = parseInt(getComputedStyle(button).paddingLeft) || 0;
const paddingRight = parseInt(getComputedStyle(button).paddingRight) || 0;
const availableWidth = buttonWidth - paddingLeft - paddingRight - 4; // 额外留4px边距
// 创建临时元素测量文本宽度
const tempSpan = document.createElement('span');
tempSpan.style.visibility = 'hidden';
tempSpan.style.position = 'absolute';
tempSpan.style.whiteSpace = 'nowrap';
tempSpan.style.fontSize = button.dataset.originalFontSize || '15px';
tempSpan.textContent = button.dataset.originalText;
document.body.appendChild(tempSpan);
const textWidth = tempSpan.offsetWidth;
document.body.removeChild(tempSpan);
// 如果文本宽度大于可用宽度,则缩小字体
if (textWidth > availableWidth) {
const scaleFactor = availableWidth / textWidth;
const originalFontSize = parseInt(button.dataset.originalFontSize) || 15;
// 限制字体最小缩小到11px(原为9px),确保字体不会太小
const newFontSize = Math.max(11, Math.floor(originalFontSize * scaleFactor));
button.style.fontSize = `${newFontSize}px`;
} else {
// 恢复原始字体大小
button.style.fontSize = button.dataset.originalFontSize || '15px';
}
// 确保显示完整文本
button.textContent = button.dataset.originalText;
}
// 调整按钮布局 - 添加新函数用于根据按钮数量自动调整布局
export function adjustButtonsLayout(buttonsContainer) {
if (!buttonsContainer) return;
const buttons = buttonsContainer.querySelectorAll('.layout-btn');
const count = buttons.length;
if (count === 0) return;
// 根据按钮数量决定每行显示多少个按钮
let buttonsPerRow;
if (count <= 3) {
// 1-3个按钮时,放在一行
buttonsPerRow = count;
} else if (count === 4) {
// 4个按钮时,每行2个
buttonsPerRow = 2;
} else {
// 5个及以上按钮时,每行3个
buttonsPerRow = 3;
}
// 计算每行宽度的百分比
const widthPercent = Math.floor(100 / buttonsPerRow);
// 应用样式到每个按钮
buttons.forEach((button, index) => {
// 计算当前按钮是当前行的第几个按钮
const positionInRow = index % buttonsPerRow;
// 设置按钮宽度
button.style.flexBasis = `calc(${widthPercent}% - 8px)`;
// 每行最后一个按钮需处理外边距
if (positionInRow === buttonsPerRow - 1) {
button.style.marginRight = '0';
}
// 调整文字大小以适应按钮
adjustTextFontSize(button);
});
}
// 创建模式选择器容器
export function createModeContainer(labelText) {
const modeContainer = document.createElement('div');
modeContainer.className = 'layout-mode-container';
modeContainer.style.display = 'flex';
modeContainer.style.flexDirection = 'column';
modeContainer.style.width = '100%';
modeContainer.style.marginTop = '10px';
modeContainer.style.gap = '8px';
const modeLabel = document.createElement('div');
modeLabel.className = 'layout-mode-label';
modeLabel.textContent = labelText;
modeLabel.style.fontSize = '14px';
modeContainer.appendChild(modeLabel);
return { modeContainer, modeLabel };
}
// 创建下拉选择框
export function createSelectBox() {
const selectBox = document.createElement('select');
selectBox.className = 'layout-select';
selectBox.style.width = '100%';
selectBox.style.padding = '6px 10px';
selectBox.style.borderRadius = '6px';
selectBox.style.outline = 'none';
return selectBox;
}
// ==========================================================
// 添加选项到选择框
// ==========================================================
export function addOptionsToSelect(selectBox, options) {
options.forEach(option => {
const optionElement = document.createElement('option');
optionElement.value = option.value;
optionElement.textContent = option.text;
selectBox.appendChild(optionElement);
});
return selectBox;
}
// ==========================================================
// 透明度管理系统 - 集中管理所有透明度相关功能
// ==========================================================
// 默认透明度值
const DEFAULT_OPACITY = {
panel: 0.85, // 面板背景默认透明度 (85%)
buttons: 0.90 // 按钮默认透明度 (90%)
};
// 当前透明度设置
let currentOpacity = {
panel: DEFAULT_OPACITY.panel,
buttons: DEFAULT_OPACITY.buttons
};
// 设置面板背景透明度
export function setPanelBackgroundOpacity(container, opacity) {
if (!container) return;
// 将百分比值转换为0-1范围的小数
const opacityValue = Math.max(10, Math.min(100, Number(opacity))) / 100;
currentOpacity.panel = opacityValue;
// 存储透明度值到数据属性,方便主题系统读取
container.dataset.bgOpacity = String(opacityValue);
// 设置背景容器的透明度
const bgContainer = container.querySelector('.theme-svg-background');
if (bgContainer) {
bgContainer.style.opacity = String(opacityValue);
}
// 调整面板整体透明度 - 背景透明但内容清晰
_updatePanelStyles(container);
// 通知当前主题更新透明度设置
_notifyThemeOpacityChange(container, opacityValue);
return opacityValue;
}
// 设置按钮透明度
export function setButtonsOpacity(container, opacity) {
if (!container) return;
// 将百分比值转换为0-1范围的小数
const opacityValue = Math.max(10, Math.min(100, Number(opacity))) / 100;
currentOpacity.buttons = opacityValue;
// 存储透明度值到数据属性
container.dataset.btnOpacity = String(opacityValue);
// 获取当前按钮背景颜色基础值(不含透明度)
const getCurrentButtonBaseColor = (button) => {
try {
const currentBg = window.getComputedStyle(button).backgroundColor;
// 从rgba中提取rgb部分
if (currentBg.includes('rgba')) {
return currentBg.replace(/rgba\((.+?), [\d\.]+\)/, 'rgba($1, ' + opacityValue + ')');
}
// 如果是rgb,转换为rgba
else if (currentBg.includes('rgb')) {
return currentBg.replace(/rgb\((.+?)\)/, 'rgba($1, ' + opacityValue + ')');
}
return currentBg;
} catch (e) {
console.warn('获取按钮颜色失败:', e);
// 根据当前主题提供默认颜色
if (currentTheme === '古神之眼') {
return 'rgba(106, 90, 205, ' + opacityValue + ')';
} else {
return 'rgba(0, 180, 220, ' + opacityValue + ')';
}
}
};
// 获取所有按钮并设置透明度
const buttons = container.querySelectorAll('.layout-btn');
if (buttons.length > 0) {
buttons.forEach(button => {
button.style.backgroundColor = getCurrentButtonBaseColor(button);
});
}
return opacityValue;
}
// 保留setPanelOpacity函数,但将其实现为只调用背景透明度函数
// 这样可以保持与原来代码的兼容性
export function setPanelOpacity(container, opacity) {
// 调用背景透明度设置函数,不再同时设置按钮透明度
return setPanelBackgroundOpacity(container, opacity);
}
// 获取当前面板透明度设置
export function getPanelOpacity() {
return {
panel: currentOpacity.panel * 100, // 转换为百分比形式
buttons: currentOpacity.buttons * 100
};
}
// 更新面板整体样式
function _updatePanelStyles(container) {
if (!container) return;
// 获取背景透明度
const bgOpacity = parseFloat(container.dataset.bgOpacity || currentOpacity.panel);
// 设置适当的背景模糊效果 - 透明度越高模糊越少
const blurAmount = Math.round(14 - bgOpacity * 8); // 根据透明度计算模糊值
container.style.backdropFilter = `blur(${blurAmount}px)`;
container.style.webkitBackdropFilter = `blur(${blurAmount}px)`;
// 设置适当的边框透明度 - 透明度低时边框更明显
const borderOpacity = Math.min(0.6, 0.8 - bgOpacity * 0.3);
const borderRGBA = container.classList.contains('theme-dark')
? `rgba(0, 0, 0, ${borderOpacity})`
: `rgba(255, 255, 255, ${borderOpacity})`;
// 只设置边框颜色,不修改边框宽度和样式
if (container.style.border) {
container.style.border = container.style.border.replace(/rgba?\(.+?\)/, borderRGBA);
}
}
// 通知当前主题透明度变化
function _notifyThemeOpacityChange(container, opacity) {
if (!container) return;
try {
// 获取当前主题ID
const themeId = currentTheme;
if (themeId) {
const theme = getTheme(themeId);
if (theme && typeof theme.setPanelOpacity === 'function') {
theme.setPanelOpacity(opacity);
}
}
} catch (e) {
console.warn('无法通过主题系统设置透明度:', e);
}
}
// ==========================================================
// 硬币元素创建相关
// ==========================================================
// 创建硬币元素
export function createCoinElement() {
const coinElement = document.createElement('div');
coinElement.className = 'layout-coin-container';
coinElement.style.width = '180px';
coinElement.style.height = '180px';
coinElement.style.margin = '5px auto 25px auto';
coinElement.style.position = 'relative';
coinElement.style.perspective = '1600px'; // 增强3D效果
return coinElement;
}
// 翻转硬币动画
export function flipCoin(coinElement) {
if (!coinElement) return;
// 简单切换flipped类来触发动画
coinElement.classList.toggle('flipped');
// 如果当前主题有自定义的翻转功能,则调用
const currentTheme = getTheme(currentTheme);
if (currentTheme && typeof currentTheme.flipCoin === 'function') {
currentTheme.flipCoin(coinElement);
}
}
// ==========================================================
// 面板位置计算函数
// ==========================================================
// 在鼠标位置显示面板
export function showAtMousePosition(container, mousePosition) {
if (!container) return;
// 修改导入路径 - 从相对路径调整为同目录导入
try {
const { calculatePanelPosition } = require('./UIUtils.js');
const position = calculatePanelPosition(mousePosition, container);
container.style.left = `${position.left}px`;
container.style.top = `${position.top}px`;
container.style.transform = 'none'; // 不再使用 transform 进行定位
} catch (e) {
// 如果导入失败,使用简单的居中逻辑
const width = container.offsetWidth || 300;
const height = container.offsetHeight || 400;
container.style.left = `${mousePosition.x - width/2}px`;
container.style.top = `${mousePosition.y - height/2}px`;
container.style.transform = 'none';
}
}
// ==========================================================
// 通知相关函数
// ==========================================================
// 显示通知信息
export function showNotification(message, type = 'error') {
const notification = document.createElement('div');
notification.className = 'layout-notification';
notification.textContent = message;
// 通知基本样式 - 保留,因为通知应该有一致的UI体验
Object.assign(notification.style, {
position: 'fixed',
top: '24px',
left: '50%',
transform: 'translateX(-50%) translateY(-20px)',
padding: '12px 24px',
borderRadius: '8px',
fontSize: '14px',
fontWeight: '500',
zIndex: '10000',
transition: 'all 0.3s ease',
backdropFilter: 'blur(8px)',
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.2)',
border: '1px solid rgba(255, 255, 255, 0.1)',
width: 'auto',
minWidth: '240px',
maxWidth: '90%',
textAlign: 'center',
opacity: '0'
});
if (type === 'info') {
notification.style.backgroundColor = 'rgba(20, 120, 60, 0.85)';
notification.style.color = 'white';
} else if (type === 'warn') {
notification.style.backgroundColor = 'rgba(230, 150, 10, 0.85)';
notification.style.color = 'white';
} else {
notification.style.backgroundColor = 'rgba(200, 0, 0, 0.85)';
notification.style.color = 'white';
}
document.body.appendChild(notification);
// 显示动画 - 从上方移入
setTimeout(() => {
notification.style.opacity = '1';
notification.style.transform = 'translateX(-50%) translateY(0)';
}, 10);
setTimeout(() => {
notification.style.opacity = '0';
notification.style.transform = 'translateX(-50%) translateY(-20px)'; // 向上方消失
setTimeout(() => {
if (notification.parentNode) {
document.body.removeChild(notification);
}
}, 300);
}, 3000);
}
+214
View File
@@ -0,0 +1,214 @@
// 智能布局 通用工具模块
// ==========================================================
// ComfyUI应用和节点相关函数
// ==========================================================
// 获取ComfyUI应用实例
export function getComfyUIApp() {
if (window.app?.canvas && window.app?.graph) {
return window.app;
}
if (window.LiteGraph?.LGraphCanvas?.active_canvas) {
const canvas = LiteGraph.LGraphCanvas.active_canvas;
if (canvas?.graph) {
return { canvas, graph: canvas.graph };
}
}
const canvasElement = document.querySelector(".litegraph.litegraph-canvas");
if (canvasElement?.lgraphcanvas) {
const canvas = canvasElement.lgraphcanvas;
if (canvas?.graph) {
return { canvas, graph: canvas.graph };
}
}
return null;
}
// 获取选中的节点
export function getSelectedNodes(app) {
if (app.canvas.selected_nodes?.length) {
return Array.from(app.canvas.selected_nodes);
}
const selectedNodes = [];
if (app.graph?._nodes) {
for (const node of app.graph._nodes) {
if (node.is_selected) {
selectedNodes.push(node);
}
}
}
return selectedNodes;
}
// 获取选中的组
export function getSelectedGroups(app) {
const selectedGroups = [];
if (app.canvas?.selected_groups?.length) {
return Array.from(app.canvas.selected_groups);
}
if (app.graph?.groups) {
for (const group of app.graph.groups) {
if (group.selected) {
selectedGroups.push(group);
}
}
}
return selectedGroups;
}
// ==========================================================
// 颜色生成和处理函数
// ==========================================================
// 生成随机颜色
export function getRandomColor() {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
// ==========================================================
// 节点排列和同步相关功能
// ==========================================================
// 判断节点排列主方向
export function getMainDirection(centers) {
let dx = 0, dy = 0;
for (let i = 1; i < centers.length; i++) {
dx += Math.abs(centers[i].x - centers[0].x);
dy += Math.abs(centers[i].y - centers[0].y);
}
return dx > dy ? 'horizontal' : 'vertical';
}
// 计算节点中心位置
export function getNodeCenters(nodes) {
return nodes.map(node => ({
node,
x: node.pos ? node.pos[0] + (node.size ? node.size[0] / 2 : 0) : 0,
y: node.pos ? node.pos[1] + (node.size ? node.size[1] / 2 : 0) : 0
}));
}
// 同步节点大小
export function syncNodeSize(nodes) {
let maxW = 0, maxH = 0;
nodes.forEach(node => {
if (node.size) {
maxW = Math.max(maxW, node.size[0]);
maxH = Math.max(maxH, node.size[1]);
}
});
nodes.forEach(node => {
if (node.size) {
node.size[0] = maxW;
node.size[1] = maxH;
}
});
return { width: maxW, height: maxH };
}
// 同步节点宽高(根据排列方向)
export function syncNodeWidthHeight(nodes) {
const centers = getNodeCenters(nodes);
const mainDir = getMainDirection(centers);
if (mainDir === 'horizontal') {
// 横向排列,同步高度
let maxH = 0;
nodes.forEach(node => {
if (node.size) maxH = Math.max(maxH, node.size[1]);
});
nodes.forEach(node => {
if (node.size) node.size[1] = maxH;
});
return { direction: 'horizontal', height: maxH };
} else {
// 纵向排列,同步宽度
let maxW = 0;
nodes.forEach(node => {
if (node.size) maxW = Math.max(maxW, node.size[0]);
});
nodes.forEach(node => {
if (node.size) node.size[0] = maxW;
});
return { direction: 'vertical', width: maxW };
}
}
// 对齐节点(根据排列方向)
export function alignNodes(nodes) {
const centers = getNodeCenters(nodes);
const mainDir = getMainDirection(centers);
if (mainDir === 'horizontal') {
// 横向排列,对齐Y轴
const avgY = centers.reduce((sum, c) => sum + c.y, 0) / centers.length;
nodes.forEach(node => {
if (node.pos) node.pos[1] = avgY - (node.size ? node.size[1] / 2 : 0);
});
return { direction: 'horizontal', y: avgY };
} else {
// 纵向排列,对齐X轴
const avgX = centers.reduce((sum, c) => sum + c.x, 0) / centers.length;
nodes.forEach(node => {
if (node.pos) node.pos[0] = avgX - (node.size ? node.size[0] / 2 : 0);
});
return { direction: 'vertical', x: avgX };
}
}
// ==========================================================
// 面板位置和大小计算
// ==========================================================
// 计算面板在鼠标位置的居中定位
export function calculatePanelPosition(mousePosition, container) {
if (!container) return { left: 0, top: 0 };
const width = container.offsetWidth || 300; // 假设默认宽度为300px
const height = container.offsetHeight || 400; // 假设默认高度为400px
// 确保面板完全在视口内
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
let left = mousePosition.x - width/2;
let top = mousePosition.y - height/2;
// 防止面板超出右边界
if (left + width > viewportWidth) {
left = viewportWidth - width - 10;
}
// 防止面板超出左边界
if (left < 10) {
left = 10;
}
// 防止面板超出下边界
if (top + height > viewportHeight) {
top = viewportHeight - height - 10;
}
// 防止面板超出上边界
if (top < 10) {
top = 10;
}
return { left, top };
}
@@ -0,0 +1,468 @@
/**
* AncientGod1.js - 古神之眼 紫色版
* 这个文件将SVG内容转换为可在JavaScript中直接使用的格式
* 使用方法:调用createAncientGod1Eye函数,传入目标DOM元素ID和可选的配置参数
*/
(function(global) {
// SVG内容作为字符串存储
const svgContent = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet">
<!-- 古神之眼SVG:星盘与齿轮 --> <defs>
<linearGradient id="destinyGlow" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#9370DB" stop-opacity="1" />
<stop offset="100%" stop-color="#483D8B" stop-opacity="1" />
</linearGradient>
<!-- 脉动模糊滤镜 -->
<filter id="pulsatingBlur" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.2" result="blur">
<animate attributeName="stdDeviation" values="1.2;1.8;1.2" dur="5s" repeatCount="indefinite" />
</feGaussianBlur>
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<!-- 金属质感齿轮渐变 -->
<linearGradient id="gearGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#8A6FD0" />
<stop offset="30%" stop-color="#63438F" />
<stop offset="70%" stop-color="#533178" />
<stop offset="100%" stop-color="#422261" />
</linearGradient>
<!-- 齿轮发光边缘 -->
<filter id="glowingEdge" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="0.8" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<!-- 齿轮阴影滤镜 -->
<filter id="gearShadow">
<feGaussianBlur in="SourceAlpha" stdDeviation="1" />
<feOffset dx="0.5" dy="0.5" result="offsetblur" />
<feFlood flood-color="#000000" flood-opacity="0.4" />
<feComposite in2="offsetblur" operator="in" />
<feMerge>
<feMergeNode />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<!-- 背景元素组 - 添加旋转动画 -->
<g id="background">
<!-- 添加旋转动画 -->
<animateTransform
attributeName="transform"
type="rotate"
from="0 50 50"
to="360 50 50"
dur="60s"
repeatCount="indefinite" />
<!-- 背景圆形 -->
<circle cx="50" cy="50" r="45" fill="url(#destinyGlow)" stroke="#B08DE1" stroke-width="1.5" />
<!-- 克苏鲁风格脉动辉光 -->
<circle cx="50" cy="50" r="42" fill="none" stroke="#6A5ACD" stroke-width="0.5" opacity="0.3" filter="url(#pulsatingBlur)">
<animate attributeName="r" values="42;43;42" dur="8s" repeatCount="indefinite" />
<animate attributeName="opacity" values="0.3;0.5;0.3" dur="8s" repeatCount="indefinite" />
</circle>
<circle cx="50" cy="50" r="38" fill="#341E6A" opacity="0.4">
<animate attributeName="r" values="38;40;38" dur="10s" repeatCount="indefinite" />
</circle>
<!-- 星盘元素 -->
<circle cx="50" cy="50" r="35" fill="none" stroke="#FFFFFF" stroke-width="0.5" opacity="0.7" />
<circle cx="50" cy="50" r="25" fill="none" stroke="#FFFFFF" stroke-width="0.5" opacity="0.8" />
<!-- 神秘符文 -->
<g opacity="0.6">
<!-- 符文1 - 外圈 -->
<path d="M50,15 L55,15 L53,18 Z" fill="none" stroke="#FFFFFF" stroke-width="0.3" />
<path d="M85,50 L85,55 L82,53 Z" fill="none" stroke="#FFFFFF" stroke-width="0.3" />
<path d="M50,85 L45,85 L47,82 Z" fill="none" stroke="#FFFFFF" stroke-width="0.3" />
<path d="M15,50 L15,45 L18,47 Z" fill="none" stroke="#FFFFFF" stroke-width="0.3" />
<!-- 符文2 - 内圈不规则符号 -->
<path d="M40,25 L45,27 L43,31 L38,29 Z" fill="none" stroke="#FFFFFF" stroke-width="0.3" />
<path d="M75,40 L73,45 L69,43 L71,38 Z" fill="none" stroke="#FFFFFF" stroke-width="0.3" />
<path d="M60,75 L55,73 L57,69 L62,71 Z" fill="none" stroke="#FFFFFF" stroke-width="0.3" />
<path d="M25,60 L27,55 L31,57 L29,62 Z" fill="none" stroke="#FFFFFF" stroke-width="0.3" />
</g>
</g>
<!-- 主齿轮与克苏鲁之眼 - 行星中心 -->
<g transform="translate(50, 50)">
<!-- 齿轮轮廓 - 增强质感 -->
<path d="M0,-20 L5,-18 L7,-23 L12,-22 L10,-16 L15,-14 L18,-19 L22,-16 L20,-11 L25,-8 L28,-12 L30,-8 L26,-4 L30,0 L26,4 L30,8 L28,12 L25,8 L20,11 L22,16 L18,19 L15,14 L10,16 L12,22 L7,23 L5,18 L0,20 L-5,18 L-7,23 L-12,22 L-10,16 L-15,14 L-18,19 L-22,16 L-20,11 L-25,8 L-28,12 L-30,8 L-26,4 L-30,0 L-26,-4 L-30,-8 L-28,-12 L-25,-8 L-20,-11 L-22,-16 L-18,-19 L-15,-14 L-10,-16 L-12,-22 L-7,-23 L-5,-18 Z"
fill="url(#gearGradient)" stroke="#DDC6FF" stroke-width="1" filter="url(#gearShadow)">
<!-- 生物机械感 - 微变形 -->
<animate attributeName="d"
values="M0,-20 L5,-18 L7,-23 L12,-22 L10,-16 L15,-14 L18,-19 L22,-16 L20,-11 L25,-8 L28,-12 L30,-8 L26,-4 L30,0 L26,4 L30,8 L28,12 L25,8 L20,11 L22,16 L18,19 L15,14 L10,16 L12,22 L7,23 L5,18 L0,20 L-5,18 L-7,23 L-12,22 L-10,16 L-15,14 L-18,19 L-22,16 L-20,11 L-25,8 L-28,12 L-30,8 L-26,4 L-30,0 L-26,-4 L-30,-8 L-28,-12 L-25,-8 L-20,-11 L-22,-16 L-18,-19 L-15,-14 L-10,-16 L-12,-22 L-7,-23 L-5,-18 Z;
M0,-19 L5,-17 L8,-23 L13,-22 L11,-15 L16,-14 L19,-20 L22,-15 L19,-10 L25,-7 L29,-12 L31,-7 L27,-4 L31,0 L27,4 L31,8 L29,13 L25,7 L19,12 L22,17 L18,20 L16,13 L11,16 L13,23 L8,24 L5,17 L0,21 L-5,17 L-7,23 L-12,22 L-10,16 L-15,14 L-18,19 L-22,16 L-20,11 L-25,8 L-28,12 L-30,8 L-26,4 L-30,0 L-26,-4 L-30,-7 L-29,-12 L-25,-7 L-19,-10 L-22,-15 L-19,-20 L-16,-14 L-11,-15 L-13,-22 L-8,-23 L-5,-17 Z;
M0,-20 L5,-18 L7,-23 L12,-22 L10,-16 L15,-14 L18,-19 L22,-16 L20,-11 L25,-8 L28,-12 L30,-8 L26,-4 L30,0 L26,4 L30,8 L28,12 L25,8 L20,11 L22,16 L18,19 L15,14 L10,16 L12,22 L7,23 L5,18 L0,20 L-5,18 L-7,23 L-12,22 L-10,16 L-15,14 L-18,19 L-22,16 L-20,11 L-25,8 L-28,12 L-30,8 L-26,4 L-30,0 L-26,-4 L-30,-8 L-28,-12 L-25,-8 L-20,-11 L-22,-16 L-18,-19 L-15,-14 L-10,-16 L-12,-22 L-7,-23 L-5,-18 Z"
dur="20s"
repeatCount="indefinite"
calcMode="spline"
keySplines="0.42 0 0.58 1; 0.42 0 0.58 1" />
</path>
<!-- 发光齿轮边缘 -->
<path d="M0,-20 L5,-18 L7,-23 L12,-22 L10,-16 L15,-14 L18,-19 L22,-16 L20,-11 L25,-8 L28,-12 L30,-8 L26,-4 L30,0 L26,4 L30,8 L28,12 L25,8 L20,11 L22,16 L18,19 L15,14 L10,16 L12,22 L7,23 L5,18 L0,20 L-5,18 L-7,23 L-12,22 L-10,16 L-15,14 L-18,19 L-22,16 L-20,11 L-25,8 L-28,12 L-30,8 L-26,4 L-30,0 L-26,-4 L-30,-8 L-28,-12 L-25,-8 L-20,-11 L-22,-16 L-18,-19 L-15,-14 L-10,-16 L-12,-22 L-7,-23 L-5,-18 Z"
fill="none" stroke="#DDC6FF" stroke-width="0.3" opacity="0.6" filter="url(#glowingEdge)">
<animate attributeName="opacity" values="0.6;0.2;0.6" dur="8s" repeatCount="indefinite" />
</path>
<!-- 微血管效果 -->
<g stroke="#AA3377" stroke-width="0.1" opacity="0.3">
<path d="M-15,-10 Q-10,-5 -15,0 Q-10,5 -5,0" />
<path d="M5,-15 Q10,-10 15,-15 Q10,-5 15,0" />
<path d="M-5,15 Q0,10 5,15 Q0,5 -5,5" />
<path d="M-10,-5 Q-5,0 0,-5 Q5,0 10,-5" />
<path d="M-8,8 Q-4,4 0,8 Q4,4 8,8" />
<!-- 脉动动画 -->
<animate attributeName="opacity" values="0.3;0.1;0.3" dur="10s" repeatCount="indefinite" />
</g>
<!-- 眼白(取代内圆) - 保持克苏鲁眼睛特性 -->
<circle cx="0" cy="0" r="12.8" fill="#2A1E4A" stroke="#B08DE1"/>
<!-- 眼球内部花纹 -->
<path d="M-9,0 A9,12 0 0,1 9,0 A9,12 0 0,1 -9,0"
fill="none" stroke="#8A2BE2" stroke-width="0.3" opacity="0.7" />
<path d="M0,-9 A12,9 0 0,1 0,9 A12,9 0 0,1 0,-9"
fill="none" stroke="#8A2BE2" stroke-width="0.3" opacity="0.7" />
<!-- 虹膜 -->
<circle cx="0" cy="0" r="8" fill="#341E6A" stroke="#9370DB" stroke-width="0.3">
<!-- 虹膜脉动动画 -->
<animate attributeName="r" values="7.8;8.2;7.8" dur="3s" repeatCount="indefinite" />
</circle>
<!-- 眼瞳(不规则移动)-->
<g class="eyeball">
<!-- 复合动画:眼瞳不规则移动 -->
<animateTransform
attributeName="transform"
type="translate"
values="0,0; 2,-1; -1,-2; -2,1; 3,2; 1,3; -3,-1; -1,2; 2,2; 0,0"
keyTimes="0;0.11;0.22;0.33;0.44;0.55;0.66;0.77;0.88;1"
dur="15s"
repeatCount="indefinite"
calcMode="spline"
keySplines="0.42 0 0.58 1; 0.42 0 0.58 1; 0.42 0 0.58 1; 0.42 0 0.58 1; 0.42 0 0.58 1; 0.42 0 0.58 1; 0.42 0 0.58 1; 0.42 0 0.58 1; 0.42 0 0.58 1" />
<!-- 瞳孔 -->
<circle cx="0" cy="0" r="3.5" fill="#090418" />
</g>
</g>
<g id="stars">
<!-- 添加与背景相同的旋转动画 -->
<animateTransform
attributeName="transform"
type="rotate"
from="0 50 50"
to="360 50 50"
dur="60s"
repeatCount="indefinite" />
<!-- 明亮星星 -->
<g fill="#FFFFFF">
<circle cx="15" cy="35" r="0.6" opacity="0.8" filter="url(#glowingEdge)">
<animate attributeName="opacity" values="0.8;0.3;0.8" dur="3s" repeatCount="indefinite" />
<animate attributeName="r" values="0.6;0.8;0.6" dur="6s" repeatCount="indefinite" />
</circle>
<circle cx="85" cy="65" r="0.7" opacity="0.8" filter="url(#glowingEdge)">
<animate attributeName="opacity" values="0.8;0.3;0.8" dur="4s" repeatCount="indefinite" />
<animate attributeName="r" values="0.7;0.9;0.7" dur="7s" repeatCount="indefinite" />
</circle>
<circle cx="40" cy="15" r="0.5" opacity="0.8" filter="url(#glowingEdge)">
<animate attributeName="opacity" values="0.8;0.3;0.8" dur="5s" repeatCount="indefinite" />
<animate attributeName="r" values="0.5;0.7;0.5" dur="5s" repeatCount="indefinite" />
</circle>
<circle cx="60" cy="85" r="0.6" opacity="0.8" filter="url(#glowingEdge)">
<animate attributeName="opacity" values="0.8;0.3;0.8" dur="4.5s" repeatCount="indefinite" />
<animate attributeName="r" values="0.6;0.8;0.6" dur="8s" repeatCount="indefinite" />
</circle>
</g>
<!-- 装饰星星 - 额外增加 -->
<g fill="#FFFFFF">
<circle cx="20" cy="20" r="0.4" opacity="0.6">
<animate attributeName="opacity" values="0.6;0.2;0.6" dur="6s" repeatCount="indefinite" />
</circle>
<circle cx="80" cy="20" r="0.3" opacity="0.5">
<animate attributeName="opacity" values="0.5;0.15;0.5" dur="7s" repeatCount="indefinite" begin="1s" />
</circle>
<circle cx="20" cy="80" r="0.35" opacity="0.55">
<animate attributeName="opacity" values="0.55;0.2;0.55" dur="9s" repeatCount="indefinite" begin="3s" />
</circle>
<circle cx="80" cy="80" r="0.25" opacity="0.45">
<animate attributeName="opacity" values="0.45;0.1;0.45" dur="8s" repeatCount="indefinite" begin="4s" />
</circle>
<circle cx="50" cy="15" r="0.3" opacity="0.5">
<animate attributeName="opacity" values="0.5;0.15;0.5" dur="6s" repeatCount="indefinite" begin="2s" />
</circle>
<circle cx="50" cy="85" r="0.35" opacity="0.55">
<animate attributeName="opacity" values="0.55;0.2;0.55" dur="7s" repeatCount="indefinite" begin="1s" />
</circle>
<circle cx="15" cy="50" r="0.25" opacity="0.45">
<animate attributeName="opacity" values="0.45;0.1;0.45" dur="8s" repeatCount="indefinite" begin="3s" />
</circle>
<circle cx="85" cy="50" r="0.3" opacity="0.5">
<animate attributeName="opacity" values="0.5;0.15;0.5" dur="9s" repeatCount="indefinite" begin="2s" />
</circle>
</g>
</g>
</svg>`;
// 导出函数:将SVG插入到指定DOM元素
global.createAncientGod1Eye = function(targetId, options = {}) {
const container = document.getElementById(targetId);
if (!container) return;
container.innerHTML = svgContent;
// 获取SVG元素
const svg = container.querySelector('svg');
const eyeball = svg.querySelector('.eyeball');
const iris = svg.querySelector('g[transform="translate(50, 50)"] > circle[r="8"]'); // 虹膜
const sclera = svg.querySelector('g[transform="translate(50, 50)"] > circle[r="12.8"]'); // 眼白
// 存储原始瞳孔半径
const pupil = eyeball.querySelector('circle');
const originalPupilRadius = pupil ? pupil.getAttribute('r') : "3.5";
// 最大移动范围(像素)
const maxEyeMovement = 4;
const maxIrisMovement = 2.5; // 虹膜移动范围比瞳孔小一点
// 添加变量跟踪鼠标活动状态
let mouseActive = false;
let mouseTimer = null;
// 鼠标移动监听函数
function handleMouseMove(event) {
mouseActive = true;
clearTimeout(mouseTimer);
// 暂停原有动画
const animateTransform = eyeball.querySelector('animateTransform');
if (animateTransform) {
try {
// 完全暂停动画而非设置为indefinite
animateTransform.setAttribute('repeatCount', '0');
animateTransform.setAttribute('dur', '999999s');
} catch (e) {
console.error("无法暂停动画:", e);
}
}
// 获取SVG的位置和尺寸 - 无论鼠标在哪都要获取
const svgRect = svg.getBoundingClientRect();
// 计算鼠标相对于SVG中心的位置
const centerX = svgRect.width / 2 + svgRect.left;
const centerY = svgRect.height / 2 + svgRect.top;
// 计算偏移方向
const deltaX = event.clientX - centerX;
const deltaY = event.clientY - centerY;
// 计算目光注视方向
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
const normalizedX = distance > 0 ? deltaX / distance : 0;
const normalizedY = distance > 0 ? deltaY / distance : 0;
// 计算鼠标与中心的相对距离比例(用于瞳孔大小)
// 根据距离计算瞳孔大小 - 距离越近,瞳孔越大
const maxDistance = Math.max(svgRect.width, svgRect.height) / 2;
const distanceRatio = 1 - Math.min(distance / maxDistance, 1); // 0到1之间
// 瞳孔设置 - 根据距离调整大小
if (pupil) {
const minPupilSize = 3.0;
const maxPupilSize = 4.2;
const pupilSize = minPupilSize + distanceRatio * (maxPupilSize - minPupilSize);
pupil.setAttribute('r', pupilSize.toFixed(1));
}
// 设置眼球跟随,限制在合理范围内
const eyeballMoveX = normalizedX * maxEyeMovement;
const eyeballMoveY = normalizedY * maxEyeMovement;
// 设置虹膜跟随,幅度比瞳孔小一些
const irisMoveX = normalizedX * maxIrisMovement;
const irisMoveY = normalizedY * maxIrisMovement;
// 应用变换 - 眼瞳
eyeball.style.transform = `translate(${eyeballMoveX}px, ${eyeballMoveY}px)`;
// 应用变换 - 虹膜
if (iris) {
iris.style.transform = `translate(${irisMoveX}px, ${irisMoveY}px)`;
}
// 鼠标不活动定时器,3秒后恢复原动画
mouseTimer = setTimeout(() => {
mouseActive = false;
resetEyeAnimations();
}, 3000);
}
// 重置眼睛所有动画
function resetEyeAnimations() {
// 获取当前位置,用于平滑过渡
const currentTransform = eyeball.style.transform;
let currentX = 0;
let currentY = 0;
// 如果当前有transform,解析出当前位置
if (currentTransform && currentTransform.includes('translate')) {
const match = currentTransform.match(/translate\(([^,]+)px,\s*([^)]+)px\)/);
if (match) {
currentX = parseFloat(match[1]);
currentY = parseFloat(match[2]);
}
}
// 创建平滑返回中心的过渡动画
if (currentX !== 0 || currentY !== 0) {
// 清除之前可能存在的过渡
eyeball.style.transition = '';
// 先确保移除animateTransform的所有效果
const animateTransform = eyeball.querySelector('animateTransform');
if (animateTransform) {
// 完全禁用SVG动画,直到CSS过渡完成
animateTransform.setAttribute('repeatCount', '0');
animateTransform.setAttribute('dur', '999999s');
animateTransform.setAttribute('values', '0,0; 0,0; 0,0');
}
// 在下一帧添加过渡效果
requestAnimationFrame(() => {
eyeball.style.transition = 'transform 1s cubic-bezier(0.4, 0.0, 0.2, 1)';
eyeball.style.transform = 'translate(0px, 0px)';
// 平滑过渡到中心后再恢复原始动画
setTimeout(() => {
// 完全移除CSS transform和transition
eyeball.style.transition = '';
eyeball.style.transform = '';
// 等待DOM更新完成
setTimeout(() => {
// 确保没有残留的内联样式
eyeball.removeAttribute('style');
// 重新启用SVG动画
if (animateTransform) {
// 恢复原始动画内容
animateTransform.setAttribute('values', '0,0; 2,-1; -1,-2; -2,1; 3,2; 1,3; -3,-1; -1,2; 2,2; 0,0');
animateTransform.setAttribute('dur', '15s');
animateTransform.setAttribute('repeatCount', 'indefinite');
}
}, 50);
// 同样对虹膜进行过渡处理
if (iris) {
iris.style.transition = '';
iris.style.transform = '';
iris.removeAttribute('style');
}
}, 1000); // 与过渡时间相匹配
});
} else {
// 如果已经在中心位置,直接恢复动画
eyeball.style.removeProperty('transform');
eyeball.removeAttribute('style');
const animateTransform = eyeball.querySelector('animateTransform');
if (animateTransform) {
animateTransform.setAttribute('values', '0,0; 2,-1; -1,-2; -2,1; 3,2; 1,3; -3,-1; -1,2; 2,2; 0,0');
animateTransform.setAttribute('dur', '15s');
animateTransform.setAttribute('repeatCount', 'indefinite');
}
}
// 重置瞳孔大小为原始值,添加平滑过渡
if (pupil) {
// 获取当前瞳孔大小
const currentPupilSize = parseFloat(pupil.getAttribute('r')) || parseFloat(originalPupilRadius);
const targetPupilSize = parseFloat(originalPupilRadius);
// 如果尺寸差异显著,使用动画过渡
if (Math.abs(currentPupilSize - targetPupilSize) > 0.1) {
// 创建动画过渡
const duration = 800; // 持续时间800毫秒
const startTime = performance.now();
const animate = (time) => {
const elapsedTime = time - startTime;
const progress = Math.min(elapsedTime / duration, 1);
// 使用缓动函数让动画更自然
const easeProgress = 1 - Math.pow(1 - progress, 3); // 缓出效果
const currentSize = currentPupilSize + (targetPupilSize - currentPupilSize) * easeProgress;
pupil.setAttribute('r', currentSize.toFixed(2));
if (progress < 1) {
requestAnimationFrame(animate);
}
};
requestAnimationFrame(animate);
} else {
// 如果差异很小,直接设置
pupil.setAttribute('r', originalPupilRadius);
}
}
// 重置虹膜,也添加平滑过渡
if (iris) {
const currentIrisTransform = iris.style.transform;
let currentIrisX = 0;
let currentIrisY = 0;
if (currentIrisTransform && currentIrisTransform.includes('translate')) {
const match = currentIrisTransform.match(/translate\(([^,]+)px,\s*([^)]+)px\)/);
if (match) {
currentIrisX = parseFloat(match[1]);
currentIrisY = parseFloat(match[2]);
}
}
if (currentIrisX !== 0 || currentIrisY !== 0) {
iris.style.transition = '';
requestAnimationFrame(() => {
iris.style.transition = 'transform 1s cubic-bezier(0.4, 0.0, 0.2, 1)';
iris.style.transform = 'translate(0px, 0px)';
setTimeout(() => {
iris.style.removeProperty('transform');
}, 50);
});
} else {
iris.style.transform = '';
}
}
}
// 添加鼠标离开监听
function handleMouseLeave() {
clearTimeout(mouseTimer);
mouseTimer = setTimeout(() => {
mouseActive = false;
resetEyeAnimations();
}, 1000); // 鼠标离开后1秒恢复原动画
}
// 添加事件监听器 - 改为监听整个文档的鼠标移动
document.addEventListener('mousemove', handleMouseMove);
// 返回清理函数
return function cleanup() {
document.removeEventListener('mousemove', handleMouseMove);
};
};
})(typeof window !== "undefined" ? window : this);
@@ -0,0 +1,535 @@
/**
* AncientGod2.js - 古神之眼 血色版
* 这个文件将SVG内容转换为可在JavaScript中直接使用的格式
* 使用方法:调用createAncientGod2Eye函数,传入目标DOM元素ID和可选的配置参数
*/
(function(global) {
// SVG内容作为字符串存储
const svgContent = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet">
<!-- 古神之眼SVG:血色版 -->
<defs>
<!-- 诡异血色主辉光渐变 -->
<linearGradient id="bloodGlow" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#FF2D2D" stop-opacity="1" />
<stop offset="100%" stop-color="#7A0404" stop-opacity="1" />
</linearGradient>
<!-- 脉动血色滤镜 -->
<filter id="bloodPulsatingBlur" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.2" result="blur">
<animate attributeName="stdDeviation" values="1.2;1.8;1.2" dur="5s" repeatCount="indefinite" />
</feGaussianBlur>
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<!-- 血色金属齿轮渐变 -->
<linearGradient id="bloodGearGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#FF4B4B" />
<stop offset="30%" stop-color="#B80000" />
<stop offset="70%" stop-color="#7A0404" />
<stop offset="100%" stop-color="#3A0000" />
</linearGradient>
<!-- 齿轮血色发光边缘 -->
<filter id="bloodGlowingEdge" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="0.8" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<!-- 齿轮阴影滤镜 - 更深血色 -->
<filter id="bloodGearShadow">
<feGaussianBlur in="SourceAlpha" stdDeviation="1" />
<feOffset dx="0.5" dy="0.5" result="offsetblur" />
<feFlood flood-color="#7A0404" flood-opacity="0.6" />
<feComposite in2="offsetblur" operator="in" />
<feMerge>
<feMergeNode />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<!-- 血滴效果滤镜 -->
<filter id="bloodDropEffect" x="-20%" y="-20%" width="140%" height="140%">
<feTurbulence type="fractalNoise" baseFrequency="0.05" numOctaves="2" result="noise" />
<feDisplacementMap in="SourceGraphic" in2="noise" scale="3" xChannelSelector="R" yChannelSelector="G" />
</filter>
</defs>
<!-- 背景元素组 - 血色旋转动画 -->
<g id="background">
<!-- 血色背景旋转 -->
<animateTransform
attributeName="transform"
type="rotate"
from="0 50 50"
to="360 50 50"
dur="120s"
repeatCount="indefinite" />
<!-- 血红色背景圆形 -->
<circle cx="50" cy="50" r="45" fill="url(#bloodGlow)" stroke="#FF0000" stroke-width="1.5" />
<!-- 诡异血色脉动辉光 -->
<circle cx="50" cy="50" r="42" fill="none" stroke="#FF2D2D" stroke-width="0.5" opacity="0.4" filter="url(#bloodPulsatingBlur)">
<animate attributeName="r" values="42;43;42" dur="8s" repeatCount="indefinite" />
<animate attributeName="opacity" values="0.4;0.6;0.4" dur="8s" repeatCount="indefinite" />
</circle>
<!-- 暗红色内圈 -->
<circle cx="50" cy="50" r="38" fill="#4A0000" opacity="0.6">
<animate attributeName="r" values="38;40;38" dur="10s" repeatCount="indefinite" />
</circle>
<!-- 血丝纹路 - 不规则血管效果 -->
<g stroke="#FF0000" stroke-width="0.3" opacity="0.7" fill="none">
<path d="M20,20 Q30,40 50,30 T80,20" filter="url(#bloodDropEffect)">
<animate attributeName="d" values="M20,20 Q30,40 50,30 T80,20;M22,22 Q35,45 55,33 T78,18;M20,20 Q30,40 50,30 T80,20" dur="15s" repeatCount="indefinite" />
<animate attributeName="opacity" values="0.7;0.5;0.7" dur="8s" repeatCount="indefinite" />
</path>
<path d="M25,75 Q40,60 60,70 T85,75" filter="url(#bloodDropEffect)">
<animate attributeName="d" values="M25,75 Q40,60 60,70 T85,75;M28,72 Q45,57 62,73 T82,76;M25,75 Q40,60 60,70 T85,75" dur="17s" repeatCount="indefinite" />
<animate attributeName="opacity" values="0.6;0.4;0.6" dur="9s" repeatCount="indefinite" />
</path>
<path d="M15,50 Q30,40 50,50 T85,50" filter="url(#bloodDropEffect)">
<animate attributeName="d" values="M15,50 Q30,40 50,50 T85,50;M17,48 Q32,35 52,54 T83,52;M15,50 Q30,40 50,50 T85,50" dur="16s" repeatCount="indefinite" />
<animate attributeName="opacity" values="0.7;0.5;0.7" dur="7s" repeatCount="indefinite" />
</path>
</g>
</g>
<!-- 主齿轮与克苏鲁之眼 - 行星中心 -->
<g transform="translate(50, 50)">
<!-- 齿轮轮廓 - 增强血色质感 -->
<path d="M0,-20 L5,-18 L7,-23 L12,-22 L10,-16 L15,-14 L18,-19 L22,-16 L20,-11 L25,-8 L28,-12 L30,-8 L26,-4 L30,0 L26,4 L30,8 L28,12 L25,8 L20,11 L22,16 L18,19 L15,14 L10,16 L12,22 L7,23 L5,18 L0,20 L-5,18 L-7,23 L-12,22 L-10,16 L-15,14 L-18,19 L-22,16 L-20,11 L-25,8 L-28,12 L-30,8 L-26,4 L-30,0 L-26,-4 L-30,-8 L-28,-12 L-25,-8 L-20,-11 L-22,-16 L-18,-19 L-15,-14 L-10,-16 L-12,-22 L-7,-23 L-5,-18 Z"
fill="url(#bloodGearGradient)" stroke="#FF9999" stroke-width="1" filter="url(#bloodGearShadow)">
<!-- 生物机械感 - 血肉蠕动效果 -->
<animate attributeName="d"
values="M0,-20 L5,-18 L7,-23 L12,-22 L10,-16 L15,-14 L18,-19 L22,-16 L20,-11 L25,-8 L28,-12 L30,-8 L26,-4 L30,0 L26,4 L30,8 L28,12 L25,8 L20,11 L22,16 L18,19 L15,14 L10,16 L12,22 L7,23 L5,18 L0,20 L-5,18 L-7,23 L-12,22 L-10,16 L-15,14 L-18,19 L-22,16 L-20,11 L-25,8 L-28,12 L-30,8 L-26,4 L-30,0 L-26,-4 L-30,-8 L-28,-12 L-25,-8 L-20,-11 L-22,-16 L-18,-19 L-15,-14 L-10,-16 L-12,-22 L-7,-23 L-5,-18 Z;
M0,-22 L6,-19 L9,-25 L14,-23 L12,-16 L18,-15 L20,-21 L24,-17 L21,-11 L27,-7 L30,-13 L33,-8 L28,-4 L33,0 L28,5 L33,9 L29,14 L26,7 L20,12 L24,18 L19,22 L16,14 L12,18 L15,25 L9,26 L6,19 L0,23 L-6,19 L-9,26 L-15,25 L-12,18 L-18,14 L-20,22 L-24,18 L-21,12 L-27,7 L-32,14 L-33,9 L-28,5 L-33,0 L-28,-5 L-33,-8 L-30,-13 L-26,-7 L-21,-11 L-24,-17 L-20,-21 L-18,-15 L-12,-16 L-14,-23 L-9,-25 L-6,-19 Z;
M0,-20 L5,-18 L7,-23 L12,-22 L10,-16 L15,-14 L18,-19 L22,-16 L20,-11 L25,-8 L28,-12 L30,-8 L26,-4 L30,0 L26,4 L30,8 L28,12 L25,8 L20,11 L22,16 L18,19 L15,14 L10,16 L12,22 L7,23 L5,18 L0,20 L-5,18 L-7,23 L-12,22 L-10,16 L-15,14 L-18,19 L-22,16 L-20,11 L-25,8 L-28,12 L-30,8 L-26,4 L-30,0 L-26,-4 L-30,-8 L-28,-12 L-25,-8 L-20,-11 L-22,-16 L-18,-19 L-15,-14 L-10,-16 L-12,-22 L-7,-23 L-5,-18 Z"
dur="15s"
repeatCount="indefinite"
calcMode="spline"
keySplines="0.42 0 0.58 1; 0.42 0 0.58 1" />
</path>
<!-- 血色发光齿轮边缘 -->
<path d="M0,-20 L5,-18 L7,-23 L12,-22 L10,-16 L15,-14 L18,-19 L22,-16 L20,-11 L25,-8 L28,-12 L30,-8 L26,-4 L30,0 L26,4 L30,8 L28,12 L25,8 L20,11 L22,16 L18,19 L15,14 L10,16 L12,22 L7,23 L5,18 L0,20 L-5,18 L-7,23 L-12,22 L-10,16 L-15,14 L-18,19 L-22,16 L-20,11 L-25,8 L-28,12 L-30,8 L-26,4 L-30,0 L-26,-4 L-30,-8 L-28,-12 L-25,-8 L-20,-11 L-22,-16 L-18,-19 L-15,-14 L-10,-16 L-12,-22 L-7,-23 L-5,-18 Z"
fill="none" stroke="#FF5555" stroke-width="0.3" opacity="0.8" filter="url(#bloodGlowingEdge)">
<!-- 血脉跳动 -->
<animate attributeName="opacity" values="0.8;0.4;0.8" dur="4s" repeatCount="indefinite" />
</path>
<!-- 血管效果 -->
<g stroke="#FF0000" stroke-width="0.2" opacity="0.5">
<path d="M-15,-10 Q-10,-5 -15,0 Q-10,5 -5,0" />
<path d="M5,-15 Q10,-10 15,-15 Q10,-5 15,0" />
<path d="M-5,15 Q0,10 5,15 Q0,5 -5,5" />
<path d="M-10,-5 Q-5,0 0,-5 Q5,0 10,-5" />
<path d="M-8,8 Q-4,4 0,8 Q4,4 8,8" />
<!-- 脉动动画 -->
<animate attributeName="opacity" values="0.5;0.3;0.5" dur="5s" repeatCount="indefinite" />
<animate attributeName="stroke-width" values="0.2;0.3;0.2" dur="3s" repeatCount="indefinite" />
</g>
<!-- 眼白(暗红色) -->
<circle cx="0" cy="0" r="12.8" fill="#330000" stroke="#FF0000" stroke-width="0.4"/>
<!-- 眼球内部血丝 -->
<path d="M-9,0 A9,12 0 0,1 9,0 A9,12 0 0,1 -9,0"
fill="none" stroke="#FF0000" stroke-width="0.3" opacity="0.7" />
<path d="M0,-9 A12,9 0 0,1 0,9 A12,9 0 0,1 0,-9"
fill="none" stroke="#FF0000" stroke-width="0.3" opacity="0.7" />
<!-- 血色虹膜 -->
<circle cx="0" cy="0" r="8" fill="#7A0404" stroke="#FF0000" stroke-width="0.3">
<!-- 虹膜脉动 - 更血腥的感觉 -->
<animate attributeName="r" values="7.8;8.5;7.8" dur="3s" repeatCount="indefinite" />
<animate attributeName="fill" values="#7A0404;#B80000;#7A0404" dur="5s" repeatCount="indefinite" />
</circle>
<!-- 诡异眼瞳(不规则移动)-->
<g class="eyeball">
<!-- 更加诡异的眼神移动 - 同步为与紫色之眼相同的轨迹 -->
<animateTransform
attributeName="transform"
type="translate"
values="0,0; 2,-1; -1,-2; -2,1; 3,2; 1,3; -3,-1; -1,2; 2,2; 0,0"
keyTimes="0;0.11;0.22;0.33;0.44;0.55;0.66;0.77;0.88;1"
dur="15s"
repeatCount="indefinite"
calcMode="spline"
keySplines="0.42 0 0.58 1; 0.42 0 0.58 1; 0.42 0 0.58 1; 0.42 0 0.58 1; 0.42 0 0.58 1; 0.42 0 0.58 1; 0.42 0 0.58 1; 0.42 0 0.58 1; 0.42 0 0.58 1" />
<!-- 深不见底的黑色瞳孔 - 移除脉动动画 -->
<circle cx="0" cy="0" r="4" fill="#000000" />
</g>
</g>
<!-- 血滴和血迹效果 -->
<g id="bloodSplatter">
<!-- 血滴1 -->
<path d="M15,25 Q15,20 20,20" fill="none" stroke="#FF0000" stroke-width="1" opacity="0.8">
<animate attributeName="d" values="M15,25 Q15,20 20,20;M14,24 Q13,18 19,19;M15,25 Q15,20 20,20" dur="10s" repeatCount="indefinite" />
<animate attributeName="opacity" values="0.8;0.5;0.8" dur="8s" repeatCount="indefinite" />
</path>
<!-- 血滴2 -->
<path d="M80,30 Q80,25 75,25" fill="none" stroke="#FF0000" stroke-width="1" opacity="0.7">
<animate attributeName="d" values="M80,30 Q80,25 75,25;M81,31 Q82,26 74,26;M80,30 Q80,25 75,25" dur="12s" repeatCount="indefinite" />
<animate attributeName="opacity" values="0.7;0.4;0.7" dur="9s" repeatCount="indefinite" />
</path>
<!-- 血滴3 -->
<path d="M30,75 Q35,75 35,70" fill="none" stroke="#FF0000" stroke-width="1" opacity="0.9">
<animate attributeName="d" values="M30,75 Q35,75 35,70;M29,74 Q34,73 33,68;M30,75 Q35,75 35,70" dur="11s" repeatCount="indefinite" />
<animate attributeName="opacity" values="0.9;0.6;0.9" dur="7s" repeatCount="indefinite" />
</path>
<!-- 血迹斑点 -->
<circle cx="20" cy="65" r="1.5" fill="#B80000" opacity="0.7" />
<circle cx="22" cy="67" r="0.8" fill="#B80000" opacity="0.8" />
<circle cx="18" cy="64" r="0.6" fill="#B80000" opacity="0.6" />
<circle cx="75" cy="40" r="1.2" fill="#B80000" opacity="0.7" />
<circle cx="78" cy="42" r="0.7" fill="#B80000" opacity="0.8" />
<circle cx="73" cy="38" r="0.5" fill="#B80000" opacity="0.6" />
<circle cx="45" cy="80" r="1.3" fill="#B80000" opacity="0.7" />
<circle cx="48" cy="82" r="0.9" fill="#B80000" opacity="0.8" />
<circle cx="43" cy="78" r="0.7" fill="#B80000" opacity="0.6" />
</g>
<!-- 诡异漂浮的血色粒子 -->
<g id="bloodParticles">
<circle cx="25" cy="35" r="0.7" fill="#FF0000" opacity="0.7" filter="url(#bloodGlowingEdge)">
<animate attributeName="cx" values="25;28;22;30;25" dur="7s" repeatCount="indefinite" />
<animate attributeName="cy" values="35;32;38;30;35" dur="8s" repeatCount="indefinite" />
<animate attributeName="r" values="0.7;1.2;0.5;1;0.7" dur="6s" repeatCount="indefinite" />
</circle>
<circle cx="75" cy="65" r="0.9" fill="#B80000" opacity="0.6" filter="url(#bloodGlowingEdge)">
<animate attributeName="cx" values="75;70;80;77;75" dur="8s" repeatCount="indefinite" />
<animate attributeName="cy" values="65;70;60;68;65" dur="7s" repeatCount="indefinite" />
<animate attributeName="r" values="0.9;1.5;0.6;1.1;0.9" dur="7s" repeatCount="indefinite" />
</circle>
<circle cx="60" cy="25" r="0.6" fill="#7A0404" opacity="0.8" filter="url(#bloodGlowingEdge)">
<animate attributeName="cx" values="60;65;55;62;60" dur="6s" repeatCount="indefinite" />
<animate attributeName="cy" values="25;20;30;28;25" dur="9s" repeatCount="indefinite" />
<animate attributeName="r" values="0.6;1.1;0.4;0.8;0.6" dur="5s" repeatCount="indefinite" />
</circle>
<circle cx="40" cy="75" r="0.8" fill="#FF0000" opacity="0.7" filter="url(#bloodGlowingEdge)">
<animate attributeName="cx" values="40;45;35;42;40" dur="7s" repeatCount="indefinite" />
<animate attributeName="cy" values="75;70;80;78;75" dur="8s" repeatCount="indefinite" />
<animate attributeName="r" values="0.8;1.3;0.5;1;0.8" dur="8s" repeatCount="indefinite" />
</circle>
</g>
</svg>`;
// 导出函数:将SVG插入到指定DOM元素
global.createAncientGod2Eye = function(targetId, options = {}) {
const container = document.getElementById(targetId);
if (!container) return;
container.innerHTML = svgContent;
// 获取SVG元素
const svg = container.querySelector('svg');
const eyeball = svg.querySelector('.eyeball');
const iris = svg.querySelector('g[transform="translate(50, 50)"] > circle[r="8"]'); // 血色虹膜
const sclera = svg.querySelector('g[transform="translate(50, 50)"] > circle[r="12.8"]'); // 眼白(暗红色)
// 存储原始瞳孔半径
const pupil = eyeball.querySelector('circle');
const originalPupilRadius = pupil ? pupil.getAttribute('r') : "4";
// 最大移动范围(像素)- 血色版眼睛可以有更剧烈的反应
const maxEyeMovement = 5;
const maxIrisMovement = 3; // 虹膜移动范围比瞳孔小一点,但血色版幅度更大
// 添加变量跟踪鼠标活动状态
let mouseActive = false;
let mouseTimer = null;
let debugLog = false; // 调试模式
// 鼠标移动监听函数
function handleMouseMove(event) {
mouseActive = true;
clearTimeout(mouseTimer);
// 暂停原有动画
const animateTransform = eyeball.querySelector('animateTransform');
if (animateTransform) {
try {
// 完全暂停动画而非设置为indefinite
animateTransform.setAttribute('repeatCount', '0');
animateTransform.setAttribute('dur', '999999s');
} catch (e) {
console.error("无法暂停动画:", e);
}
}
// 获取SVG的位置和尺寸
const svgRect = svg.getBoundingClientRect();
// 计算鼠标相对于SVG中心的位置
const centerX = svgRect.width / 2 + svgRect.left;
const centerY = svgRect.height / 2 + svgRect.top;
// 计算偏移方向
const deltaX = event.clientX - centerX;
const deltaY = event.clientY - centerY;
// 计算眼睛注视方向
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
const normalizedX = distance > 0 ? deltaX / distance : 0;
const normalizedY = distance > 0 ? deltaY / distance : 0;
// 设置眼球跟随,限制在最大范围内
const eyeballMoveX = normalizedX * maxEyeMovement;
const eyeballMoveY = normalizedY * maxEyeMovement;
// 设置虹膜跟随,幅度比瞳孔小一些
const irisMoveX = normalizedX * maxIrisMovement;
const irisMoveY = normalizedY * maxIrisMovement;
// 应用变换 - 眼瞳
eyeball.style.transform = `translate(${eyeballMoveX}px, ${eyeballMoveY}px)`;
// 应用变换 - 虹膜
if (iris) {
iris.style.transform = `translate(${irisMoveX}px, ${irisMoveY}px)`;
// 血色版虹膜脉动效果 - 根据鼠标接近程度增强
// 临时暂停原有动画
const irisAnimateR = iris.querySelector('animate[attributeName="r"]');
if (irisAnimateR) {
irisAnimateR.setAttribute('dur', 'indefinite');
}
const irisAnimateFill = iris.querySelector('animate[attributeName="fill"]');
if (irisAnimateFill) {
irisAnimateFill.setAttribute('dur', 'indefinite');
}
// 根据距离调整虹膜大小和颜色 - 距离越近,虹膜越紧缩
const maxDistance = Math.max(svgRect.width, svgRect.height) / 2;
const distanceRatio = Math.min(distance / maxDistance, 1); // 0到1之间
const minIrisSize = 7.4; // 接近时紧缩
const maxIrisSize = 8.6; // 远离时放大
const irisSize = minIrisSize + distanceRatio * (maxIrisSize - minIrisSize);
iris.setAttribute('r', irisSize.toFixed(1));
// 距离越近,血色越深
const irisColor = distanceRatio < 0.3 ? '#6A0303' : // 非常近 - 深红色
distanceRatio < 0.6 ? '#7A0404' : // 中等距离
'#B80000'; // 远距离 - 鲜红色
iris.setAttribute('fill', irisColor);
}
// 添加血色瞳孔收缩效应 - 鼠标靠近时瞳孔扩大(血色版特性相反)
if (pupil) {
// 基于距离计算瞳孔大小
const maxDistance = Math.max(svgRect.width, svgRect.height) / 2;
const distanceRatio = Math.min(distance / maxDistance, 1); // 0到1之间
// 血色版特殊效果:越近瞳孔越大(掠食特性),相反于普通眼睛
// 显著增大变化范围,使效果更加明显
const minPupilSize = 2.8; // 远时最小
const maxPupilSize = 5.5; // 近时最大
const pupilSize = maxPupilSize - distanceRatio * (maxPupilSize - minPupilSize);
// 直接设置瞳孔大小,并确保应用生效
pupil.setAttribute('r', pupilSize.toFixed(1));
// 调试日志
if (debugLog) {
console.log(`距离比例: ${distanceRatio.toFixed(2)}, 瞳孔大小: ${pupilSize.toFixed(1)}`);
}
}
// 设置鼠标不活动定时器,2.5秒后恢复原动画(血色版反应更快)
mouseTimer = setTimeout(() => {
mouseActive = false;
resetEyeAnimations();
}, 2500);
}
// 重置眼睛所有动画
function resetEyeAnimations() {
// 获取当前位置,用于平滑过渡
const currentTransform = eyeball.style.transform;
let currentX = 0;
let currentY = 0;
// 如果当前有transform,解析出当前位置
if (currentTransform && currentTransform.includes('translate')) {
const match = currentTransform.match(/translate\(([^,]+)px,\s*([^)]+)px\)/);
if (match) {
currentX = parseFloat(match[1]);
currentY = parseFloat(match[2]);
}
}
// 创建平滑返回中心的过渡动画 - 血色版眼睛的过渡比秩序之眼更快速但仍然平滑
if (currentX !== 0 || currentY !== 0) {
// 清除之前可能存在的过渡
eyeball.style.transition = '';
// 先确保移除animateTransform的所有效果
const animateTransform = eyeball.querySelector('animateTransform');
if (animateTransform) {
// 完全禁用SVG动画,直到CSS过渡完成
animateTransform.setAttribute('repeatCount', '0');
animateTransform.setAttribute('dur', '999999s');
animateTransform.setAttribute('values', '0,0; 0,0; 0,0');
}
// 在下一帧添加过渡效果 - 血色版使用更短的过渡时间
requestAnimationFrame(() => {
eyeball.style.transition = 'transform 0.8s cubic-bezier(0.25, 0.1, 0.25, 1)';
eyeball.style.transform = 'translate(0px, 0px)';
// 过渡结束后平滑恢复原始动画
setTimeout(() => {
// 完全移除CSS transform和transition
eyeball.style.transition = '';
eyeball.style.transform = '';
// 等待DOM更新完成
setTimeout(() => {
// 确保没有残留的内联样式
eyeball.removeAttribute('style');
// 重新启用SVG动画
if (animateTransform) {
// 恢复原始动画内容
animateTransform.setAttribute('values', '0,0; 2,-1; -1,-2; -2,1; 3,2; 1,3; -3,-1; -1,2; 2,2; 0,0');
animateTransform.setAttribute('dur', '15s');
animateTransform.setAttribute('repeatCount', 'indefinite');
}
}, 50);
// 同样对虹膜进行平滑过渡
if (iris) {
iris.style.transition = '';
iris.style.transform = '';
iris.removeAttribute('style');
}
}, 800); // 与过渡时间相匹配
});
} else {
// 如果已经在中心位置,直接恢复动画
eyeball.style.removeProperty('transform');
eyeball.removeAttribute('style');
const animateTransform = eyeball.querySelector('animateTransform');
if (animateTransform) {
animateTransform.setAttribute('values', '0,0; 2,-1; -1,-2; -2,1; 3,2; 1,3; -3,-1; -1,2; 2,2; 0,0');
animateTransform.setAttribute('dur', '15s');
animateTransform.setAttribute('repeatCount', 'indefinite');
}
}
// 重置瞳孔大小为原始值,添加平滑过渡效果
if (pupil) {
// 获取当前瞳孔大小
const currentPupilSize = parseFloat(pupil.getAttribute('r')) || parseFloat(originalPupilRadius);
const targetPupilSize = parseFloat(originalPupilRadius);
// 如果尺寸差异显著,使用动画过渡
if (Math.abs(currentPupilSize - targetPupilSize) > 0.1) {
// 创建动画过渡 - 血色版动画更快
const duration = 600; // 持续时间600毫秒
const startTime = performance.now();
const animate = (time) => {
const elapsedTime = time - startTime;
const progress = Math.min(elapsedTime / duration, 1);
// 使用弹性缓动让血色版更有特点
const easeProgress = 1 - Math.pow(1 - progress, 2.5); // 略微弹性的缓出
const currentSize = currentPupilSize + (targetPupilSize - currentPupilSize) * easeProgress;
pupil.setAttribute('r', currentSize.toFixed(2));
if (progress < 1) {
requestAnimationFrame(animate);
}
};
requestAnimationFrame(animate);
} else {
// 如果差异很小,直接设置
pupil.setAttribute('r', originalPupilRadius);
}
}
// 重置虹膜,同样添加平滑过渡
if (iris) {
// 对虹膜大小和颜色也添加过渡效果
iris.style.transition = 'fill 0.8s ease';
iris.removeAttribute('r'); // 让原有动画接管大小变化
iris.removeAttribute('fill'); // 让原有动画接管颜色变化
// 处理虹膜的位置过渡
const currentIrisTransform = iris.style.transform;
let currentIrisX = 0;
let currentIrisY = 0;
if (currentIrisTransform && currentIrisTransform.includes('translate')) {
const match = currentIrisTransform.match(/translate\(([^,]+)px,\s*([^)]+)px\)/);
if (match) {
currentIrisX = parseFloat(match[1]);
currentIrisY = parseFloat(match[2]);
}
}
if (currentIrisX !== 0 || currentIrisY !== 0) {
iris.style.transition = iris.style.transition + ', transform 0.8s cubic-bezier(0.25, 0.1, 0.25, 1)';
requestAnimationFrame(() => {
iris.style.transform = 'translate(0px, 0px)';
setTimeout(() => {
iris.style.transition = '';
iris.style.transform = '';
// 恢复虹膜动画
const irisAnimateR = iris.querySelector('animate[attributeName="r"]');
if (irisAnimateR) {
irisAnimateR.setAttribute('dur', '3s');
}
const irisAnimateFill = iris.querySelector('animate[attributeName="fill"]');
if (irisAnimateFill) {
irisAnimateFill.setAttribute('dur', '5s');
}
}, 800);
});
} else {
iris.style.transform = '';
// 恢复虹膜动画
const irisAnimateR = iris.querySelector('animate[attributeName="r"]');
if (irisAnimateR) {
irisAnimateR.setAttribute('dur', '3s');
}
const irisAnimateFill = iris.querySelector('animate[attributeName="fill"]');
if (irisAnimateFill) {
irisAnimateFill.setAttribute('dur', '5s');
}
}
}
}
// 添加事件监听器 - 改为监听整个文档的鼠标移动
document.addEventListener('mousemove', handleMouseMove);
// 返回清理函数
return function cleanup() {
document.removeEventListener('mousemove', handleMouseMove);
// 不再需要移除mouseleave事件,因为它现在全局监视
};
};
})(typeof window !== "undefined" ? window : this);
@@ -0,0 +1,842 @@
/**
* AncientGodTheme.js - 古神之眼主题
* 整合秩序和混沌两种风格的面板和硬币效果
*/
import './AncientGod1.js'; // 秩序主题硬币
import './AncientGod2.js'; // 混沌主题硬币
import { injectStyles, removeStyles } from '../UIStyles.js';
// 导入模块化的面板背景SVG
import { OrderPanelSVG } from './OrderPanelStyle.js';
import { ChaosPanelSVG } from './ChaosPanelStyle.js';
// 秩序主题CSS
const OrderPanelCSS = `
.layout-panel {
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(176, 141, 225, 0.3);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 0 8px rgba(138, 43, 226, 0.2);
}
.layout-panel::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(176, 141, 225, 0.7), transparent);
}
.ancient-god-coin-container {
perspective: 1600px;
width: 180px;
height: 180px;
margin: 5px auto 25px auto;
position: relative;
}
.ancient-god-coin {
width: 100%;
height: 100%;
position: relative;
transform-style: preserve-3d;
transition: transform 0.9s cubic-bezier(.4,2,.3,1);
cursor: pointer;
}
.ancient-god-coin.flipped {
transform: rotateY(180deg);
}
.ancient-god-coin-face {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
border-radius: 50%;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
}
.ancient-god-coin-face.back {
transform: rotateY(180deg);
}
.layout-btn {
background: rgba(106, 90, 205, 0.4);
border: 1px solid rgba(176, 141, 225, 0.5);
color: #e1e1e1;
}
.layout-btn:hover {
background: rgba(106, 90, 205, 0.6);
box-shadow: 0 0 15px rgba(138, 43, 226, 0.5);
border: 1px solid rgba(176, 141, 225, 0.8);
color: white;
}
/* 移除特殊按钮样式,统一所有按钮外观 */
select {
border: 1px solid rgba(176, 141, 225, 0.4);
background-color: rgba(42, 30, 74, 0.6);
}
`;
// 混沌主题CSS
const ChaosPanelCSS = `
.layout-panel {
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 0, 0, 0.3);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 0 10px rgba(184, 0, 0, 0.2);
}
.layout-panel::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(255, 0, 0, 0.7), transparent);
}
.ancient-god-coin-container {
perspective: 1600px;
width: 180px;
height: 180px;
margin: 5px auto 25px auto;
position: relative;
}
.ancient-god-coin {
width: 100%;
height: 100%;
position: relative;
transform-style: preserve-3d;
transition: transform 0.9s cubic-bezier(.4,2,.3,1);
cursor: pointer;
}
.ancient-god-coin.flipped {
transform: rotateY(180deg);
}
.ancient-god-coin-face {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
border-radius: 50%;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
}
.ancient-god-coin-face.back {
transform: rotateY(180deg);
}
.layout-btn {
background: rgba(170, 0, 0, 0.4);
border: 1px solid rgba(255, 0, 0, 0.5);
color: #e1e1e1;
}
.layout-btn:hover {
background: rgba(170, 0, 0, 0.6);
box-shadow: 0 0 15px rgba(255, 0, 0, 0.5);
border: 1px solid rgba(255, 0, 0, 0.8);
color: white;
}
select {
border: 1px solid rgba(255, 0, 0, 0.4);
background-color: rgba(51, 0, 0, 0.6);
}
`;
// 添加主题切换过渡动画CSS
const themeTransitionCSS = `
.ancient-god-ripple-container {
pointer-events: none;
z-index: 10000;
}
.theme-svg-background {
transition: opacity 0.6s ease;
}
.layout-panel {
transition: border-color 0.8s ease, box-shadow 0.8s ease, opacity 0.5s ease !important;
}
.layout-btn {
transition: background 0.5s ease, border-color 0.5s ease, box-shadow 0.5s ease;
}
select {
transition: border-color 0.5s ease, background-color 0.5s ease;
}
.theme-old-layer, .theme-new-layer {
backface-visibility: hidden;
transform: translateZ(0);
}
.theme-new-layer {
clip-path: circle(0% at 50% 50%);
}
`;
// 主题标识符
const THEME_ID = {
PURPLE: 'purple', // 秩序主题
BLOOD: 'blood' // 混沌主题
};
let currentThemeId = THEME_ID.PURPLE;
let panelContainer = null;
let coinElement = null;
let coinFlipped = false;
let currentPanelOpacity = 0.8; // 默认透明度设置为80%
// 主题SVG定义 - 使用导入的模块
const themeSVGs = {
[THEME_ID.PURPLE]: OrderPanelSVG,
[THEME_ID.BLOOD]: ChaosPanelSVG
};
// 主题CSS样式 - 使用导入的模块
const themeCSSTemplates = {
[THEME_ID.PURPLE]: OrderPanelCSS,
[THEME_ID.BLOOD]: ChaosPanelCSS
};
// 主题样式注入函数
function injectThemeStyles() {
// 注入当前主题的CSS
removeStyles('ancient-god-theme-css');
injectStyles('ancient-god-theme-css', themeCSSTemplates[currentThemeId]);
// 注入过渡动画CSS
removeStyles('ancient-god-transition-css');
injectStyles('ancient-god-transition-css', themeTransitionCSS);
}
// 辅助函数:设置SVG元素样式,避免重复代码
function applySVGStyles(svgElement) {
if (!svgElement) return;
svgElement.style.width = '100%';
svgElement.style.height = '100%';
svgElement.style.display = 'block';
svgElement.setAttribute('preserveAspectRatio', 'xMidYMid slice');
}
// 设置SVG背景
function setSVGBackground(themeId = null) {
if (!panelContainer) return;
// 如果未指定主题ID,则使用当前主题
const targetThemeId = themeId || currentThemeId;
// 创建或获取SVG背景容器
let svgContainer = panelContainer.querySelector('.theme-svg-background');
if (!svgContainer) {
svgContainer = document.createElement('div');
svgContainer.className = 'theme-svg-background';
svgContainer.style.position = 'absolute';
svgContainer.style.top = '0';
svgContainer.style.left = '0';
svgContainer.style.width = '100%';
svgContainer.style.height = '100%';
svgContainer.style.zIndex = '-1';
svgContainer.style.borderRadius = 'inherit';
svgContainer.style.overflow = 'hidden';
// 确保SVG背景完全覆盖面板,包括边缘和内边距
svgContainer.style.boxSizing = 'border-box';
svgContainer.style.margin = '0';
svgContainer.style.padding = '0';
panelContainer.insertBefore(svgContainer, panelContainer.firstChild);
}
// 设置SVG内容
svgContainer.innerHTML = themeSVGs[targetThemeId];
// 确保SVG元素本身也完全填充容器
const svgElement = svgContainer.querySelector('svg');
if (svgElement) {
applySVGStyles(svgElement);
}
}
// 创建硬币元素
function createCoinElement() {
// 创建容器
const container = document.createElement('div');
container.className = 'ancient-god-coin-container';
// 创建硬币元素
const coin = document.createElement('div');
coin.className = 'ancient-god-coin';
// 创建正面 (紫色)
const front = document.createElement('div');
front.className = 'ancient-god-coin-face front';
front.id = 'ancient-god-eye1';
// 创建反面 (血色)
const back = document.createElement('div');
back.className = 'ancient-god-coin-face back';
back.id = 'ancient-god-eye2';
// 组装硬币
coin.appendChild(front);
coin.appendChild(back);
container.appendChild(coin);
// 初始化硬币眼睛
setTimeout(() => {
if (typeof window.createAncientGod1Eye === 'function') {
window.createAncientGod1Eye('ancient-god-eye1');
} else {
console.warn('无法找到createAncientGod1Eye函数');
}
if (typeof window.createAncientGod2Eye === 'function') {
window.createAncientGod2Eye('ancient-god-eye2');
} else {
console.warn('无法找到createAncientGod2Eye函数');
}
}, 100);
return {container, coin};
}
// 设置面板透明度 - 修改为配合UIStyles.js的统一透明度控制
export function setPanelOpacity(opacity) {
// 只保存当前透明度,实际的透明度设置由UIStyles.js处理
currentPanelOpacity = Math.max(0.1, Math.min(1.0, Number(opacity)));
// 更新数据属性,供UIStyles.js读取
if (panelContainer) {
panelContainer.dataset.opacity = String(currentPanelOpacity);
}
}
// 获取当前面板透明度
export function getPanelOpacity() {
return currentPanelOpacity;
}
// 从DOM元素或默认值恢复透明度设置
function restorePanelOpacity() {
if (!panelContainer) return;
const savedOpacity = panelContainer.dataset.opacity;
const opacity = savedOpacity ? Number(savedOpacity) : currentPanelOpacity;
setPanelOpacity(opacity);
}
// 翻转硬币并切换主题
function flipCoin() {
if (!coinElement || !panelContainer) return;
// 保存当前的透明度设置,以便稍后恢复
const savedBgOpacity = panelContainer.dataset.bgOpacity || currentPanelOpacity;
const savedBtnOpacity = panelContainer.dataset.btnOpacity || DEFAULT_OPACITY.buttons;
// 根据目标状态确定硬币的翻转方向
const targetState = !coinFlipped;
coinFlipped = targetState; // 直接设置为目标状态
// 获取当前和目标主题
const fromTheme = !targetState ? THEME_ID.BLOOD : THEME_ID.PURPLE;
const toTheme = targetState ? THEME_ID.BLOOD : THEME_ID.PURPLE;
// 更新硬币翻转状态
if (coinFlipped) {
coinElement.classList.add('flipped');
currentThemeId = THEME_ID.BLOOD;
} else {
coinElement.classList.remove('flipped');
currentThemeId = THEME_ID.PURPLE;
}
// 等待硬币翻转动画完成后再开始扩散效果
setTimeout(() => {
// 准备新旧主题层
prepareThemeLayers(fromTheme, toTheme);
// 获取硬币元素在面板中的位置
const coinRect = coinElement.getBoundingClientRect();
const panelRect = panelContainer.getBoundingClientRect();
// 计算硬币中心相对于面板的位置
const coinCenterX = (coinRect.left + coinRect.width / 2) - panelRect.left;
const coinCenterY = (coinRect.top + coinRect.height / 2) - panelRect.top;
// 计算面板对角线长度 (作为最大扩散半径)
const maxRadius = Math.sqrt(Math.pow(panelRect.width, 2) + Math.pow(panelRect.height, 2));
// 创建扩散效果容器 - 仅用于视觉效果
const rippleContainer = document.createElement('div');
rippleContainer.className = 'ancient-god-ripple-container';
rippleContainer.style.position = 'absolute';
rippleContainer.style.top = '0';
rippleContainer.style.left = '0';
rippleContainer.style.width = '100%';
rippleContainer.style.height = '100%';
rippleContainer.style.pointerEvents = 'none';
rippleContainer.style.zIndex = '10000';
rippleContainer.style.borderRadius = 'inherit';
rippleContainer.style.overflow = 'hidden';
// 创建发光扩散效果元素 - 纯视觉效果
const glowRipple = document.createElement('div');
glowRipple.className = 'ancient-god-glow-ripple';
glowRipple.style.position = 'absolute';
glowRipple.style.top = coinCenterY + 'px';
glowRipple.style.left = coinCenterX + 'px';
glowRipple.style.width = '0';
glowRipple.style.height = '0';
glowRipple.style.borderRadius = '50%';
glowRipple.style.transform = 'translate(-50%, -50%)';
glowRipple.style.pointerEvents = 'none';
glowRipple.style.background = coinFlipped
? 'radial-gradient(circle, rgba(255, 0, 0, 0.3) 0%, rgba(122, 0, 0, 0.1) 70%, transparent 100%)'
: 'radial-gradient(circle, rgba(176, 141, 225, 0.3) 0%, rgba(106, 90, 205, 0.1) 70%, transparent 100%)';
glowRipple.style.boxShadow = coinFlipped
? '0 0 20px 10px rgba(255, 0, 0, 0.3)'
: '0 0 20px 10px rgba(138, 43, 226, 0.3)';
// 添加CSS动画样式
const rippleStyle = document.createElement('style');
rippleStyle.textContent = `
@keyframes ancient-god-glow-animation {
0% {
width: 0;
height: 0;
opacity: 0.8;
}
100% {
width: ${maxRadius * 2}px;
height: ${maxRadius * 2}px;
opacity: 0;
}
}
@keyframes ancient-god-reveal-animation {
0% {
clip-path: circle(0% at ${coinCenterX}px ${coinCenterY}px);
}
100% {
clip-path: circle(${maxRadius}px at ${coinCenterX}px ${coinCenterY}px);
}
}
.theme-transition-active {
animation: ancient-god-reveal-animation 2s ease-out forwards;
}
.ancient-god-glow-ripple {
animation: ancient-god-glow-animation 2s ease-out forwards;
}
`;
document.head.appendChild(rippleStyle);
// 添加扩散元素到容器
rippleContainer.appendChild(glowRipple);
panelContainer.appendChild(rippleContainer);
// 启动新主题的显示动画
const newThemeLayer = panelContainer.querySelector('.theme-new-layer');
if (newThemeLayer) {
// 设置新主题背景的初始clip-path位置到硬币中心
newThemeLayer.style.clipPath = `circle(0% at ${coinCenterX}px ${coinCenterY}px)`;
// 让CSS接管动画,而不是通过JS重设位置
newThemeLayer.classList.add('theme-transition-active');
// 保留新主题中的SVG动画,不要重新渲染
const svgElement = newThemeLayer.querySelector('svg');
if (svgElement) {
svgElement.setAttribute('data-preserve-animation', 'true');
applySVGStyles(svgElement);
}
}
// 注入当前主题的CSS以确保应用正确的样式
injectThemeStyles();
// 动画结束后清理并恢复透明度
setTimeout(() => {
// 移除临时元素
rippleContainer.remove();
rippleStyle.remove();
// 优化主题层,但保留动画状态
cleanupThemeLayers();
// 恢复之前保存的透明度设置
if (panelContainer) {
// 设置数据属性
panelContainer.dataset.bgOpacity = savedBgOpacity;
panelContainer.dataset.btnOpacity = savedBtnOpacity;
// 应用透明度
const bgContainer = panelContainer.querySelector('.theme-svg-background');
if (bgContainer) {
bgContainer.style.opacity = savedBgOpacity;
}
// 通知主题系统透明度已更改
_updatePanelStyles(panelContainer);
}
}, 2000);
}, 900); // 等待硬币翻转动画完成(0.9秒)
}
// 准备主题过渡层
function prepareThemeLayers(fromTheme, toTheme) {
if (!panelContainer) return;
// 清除可能存在的旧层
cleanupThemeLayers();
// 创建旧主题层 (底层)
const oldLayer = document.createElement('div');
oldLayer.className = 'theme-old-layer';
oldLayer.style.position = 'absolute';
oldLayer.style.top = '0';
oldLayer.style.left = '0';
oldLayer.style.width = '100%';
oldLayer.style.height = '100%';
oldLayer.style.borderRadius = 'inherit';
oldLayer.style.zIndex = '-2';
oldLayer.style.overflow = 'hidden';
// 创建新主题层 (顶层)
const newLayer = document.createElement('div');
newLayer.className = 'theme-new-layer';
newLayer.style.position = 'absolute';
newLayer.style.top = '0';
newLayer.style.left = '0';
newLayer.style.width = '100%';
newLayer.style.height = '100%';
newLayer.style.borderRadius = 'inherit';
newLayer.style.zIndex = '-1';
newLayer.style.overflow = 'hidden';
// 初始状态下,新主题层是隐藏的(通过clip-path)
newLayer.style.clipPath = 'circle(0% at 50% 50%)';
// 为新旧层设置不同的背景和样式
// 旧主题背景
const oldBg = document.createElement('div');
oldBg.innerHTML = themeSVGs[fromTheme];
oldBg.style.width = '100%';
oldBg.style.height = '100%';
oldLayer.appendChild(oldBg);
// 确保SVG完全覆盖容器
const oldSvg = oldBg.querySelector('svg');
if (oldSvg) {
applySVGStyles(oldSvg);
}
// 新主题背景
const newBg = document.createElement('div');
newBg.innerHTML = themeSVGs[toTheme];
newBg.style.width = '100%';
newBg.style.height = '100%';
newLayer.appendChild(newBg);
// 确保SVG完全覆盖容器
const newSvg = newBg.querySelector('svg');
if (newSvg) {
applySVGStyles(newSvg);
}
// 添加主题样式
applyThemeToLayer(oldLayer, fromTheme);
applyThemeToLayer(newLayer, toTheme);
// 添加层到面板
panelContainer.insertBefore(oldLayer, panelContainer.firstChild);
panelContainer.insertBefore(newLayer, panelContainer.firstChild);
// 移除原来的SVG背景容器
const oldSvgContainer = panelContainer.querySelector('.theme-svg-background');
if (oldSvgContainer) {
oldSvgContainer.remove();
}
}
// 应用主题样式到层
function applyThemeToLayer(layer, themeId) {
// 添加边框样式
if (themeId === THEME_ID.PURPLE) {
layer.style.border = '1px solid rgba(176, 141, 225, 0.3)';
layer.style.boxShadow = '0 8px 32px rgba(0, 0, 0, 0.3), 0 0 8px rgba(138, 43, 226, 0.2)';
} else {
layer.style.border = '1px solid rgba(255, 0, 0, 0.3)';
layer.style.boxShadow = '0 8px 32px rgba(0, 0, 0, 0.3), 0 0 10px rgba(184, 0, 0, 0.2)';
}
// 添加背景模糊效果
layer.style.backdropFilter = 'blur(12px)';
layer.style.webkitBackdropFilter = 'blur(12px)';
}
// 清理主题层
function cleanupThemeLayers() {
if (!panelContainer) return;
// 保存当前透明度设置
const savedBgOpacity = panelContainer.dataset.bgOpacity || currentPanelOpacity;
// 移除旧层,但保留新层作为当前背景
const oldLayer = panelContainer.querySelector('.theme-old-layer');
if (oldLayer) oldLayer.remove();
// 将新层转换为标准背景层
const newLayer = panelContainer.querySelector('.theme-new-layer');
if (newLayer) {
// 移除动画类并重设clip-path,保持全覆盖状态
newLayer.classList.remove('theme-transition-active');
newLayer.style.clipPath = 'none';
newLayer.className = 'theme-svg-background';
newLayer.style.zIndex = '-1';
// 保留SVG中的动画状态,不要重新设置内容
const existingSvg = newLayer.querySelector('svg');
if (existingSvg && existingSvg.getAttribute('data-preserve-animation') === 'true') {
applySVGStyles(existingSvg);
}
// 恢复透明度设置
newLayer.style.opacity = savedBgOpacity;
} else {
// 如果没有新层(异常情况),则创建标准背景
setSVGBackground();
// 恢复透明度
const bgContainer = panelContainer.querySelector('.theme-svg-background');
if (bgContainer) {
bgContainer.style.opacity = savedBgOpacity;
}
}
// 添加以下代码来更新按钮颜色
setTimeout(() => {
if (panelContainer) {
const buttons = panelContainer.querySelectorAll('.layout-btn');
const btnOpacity = parseFloat(panelContainer.dataset.btnOpacity || DEFAULT_OPACITY.buttons);
buttons.forEach(button => {
// 根据当前主题设置按钮背景颜色
const bgColor = currentThemeId === THEME_ID.PURPLE
? `rgba(106, 90, 205, ${btnOpacity})` // 秩序主题 - 紫色
: `rgba(170, 0, 0, ${btnOpacity})`; // 混沌主题 - 红色
button.style.backgroundColor = bgColor;
});
}
}, 50);
}
// 主题模块接口
const AncientGodTheme = {
name: '古神之眼',
description: '一款融合秩序与混沌的主题,通过硬币翻转切换两种截然不同的视觉风格',
// 初始化主题
init(container, existingCoinEl) {
panelContainer = container;
// 确保每次初始化时硬币都处于正面状态
coinFlipped = false;
currentThemeId = THEME_ID.PURPLE;
// 创建或使用硬币元素
if (existingCoinEl) {
// 完全清空现有容器并重建硬币
existingCoinEl.innerHTML = '';
// 创建硬币元素
const coin = document.createElement('div');
coin.className = 'ancient-god-coin';
// 确保移除任何可能存在的flipped类
coin.classList.remove('flipped');
// 创建正面 (紫色)
const front = document.createElement('div');
front.className = 'ancient-god-coin-face front';
front.id = 'ancient-god-eye1';
front.style.width = '100%';
front.style.height = '100%';
front.style.position = 'absolute';
front.style.backfaceVisibility = 'hidden';
front.style.borderRadius = '50%';
// 创建反面 (血色)
const back = document.createElement('div');
back.className = 'ancient-god-coin-face back';
back.id = 'ancient-god-eye2';
back.style.width = '100%';
back.style.height = '100%';
back.style.position = 'absolute';
back.style.backfaceVisibility = 'hidden';
back.style.borderRadius = '50%';
back.style.transform = 'rotateY(180deg)';
// 组装硬币
coin.appendChild(front);
coin.appendChild(back);
existingCoinEl.appendChild(coin);
coinElement = coin;
// 初始化硬币眼睛
setTimeout(() => {
if (typeof window.createAncientGod1Eye === 'function') {
window.createAncientGod1Eye('ancient-god-eye1');
}
if (typeof window.createAncientGod2Eye === 'function') {
window.createAncientGod2Eye('ancient-god-eye2');
}
}, 100);
} else {
// 如果没有提供容器,则创建完整的硬币容器
const coinContainer = document.createElement('div');
coinContainer.className = 'ancient-god-coin-container';
// 创建硬币元素
const coin = document.createElement('div');
coin.className = 'ancient-god-coin';
// 创建正面 (紫色)
const front = document.createElement('div');
front.className = 'ancient-god-coin-face front';
front.id = 'ancient-god-eye1';
// 创建反面 (血色)
const back = document.createElement('div');
back.className = 'ancient-god-coin-face back';
back.id = 'ancient-god-eye2';
// 组装硬币
coin.appendChild(front);
coin.appendChild(back);
coinContainer.appendChild(coin);
coinElement = coin;
// 初始化硬币眼睛
setTimeout(() => {
if (typeof window.createAncientGod1Eye === 'function') {
window.createAncientGod1Eye('ancient-god-eye1');
}
if (typeof window.createAncientGod2Eye === 'function') {
window.createAncientGod2Eye('ancient-god-eye2');
}
}, 100);
// 添加到面板
if (panelContainer.firstChild) {
panelContainer.insertBefore(coinContainer, panelContainer.firstChild);
} else {
panelContainer.appendChild(coinContainer);
}
}
// 添加硬币点击事件
coinElement.addEventListener('click', (e) => {
e.stopPropagation();
this.flipCoin();
});
return true;
},
// 应用主题
applyTheme() {
injectThemeStyles();
// 总是重新初始化背景,保证背景与硬币状态同步
// 移除任何现有的背景层
const existingBgs = panelContainer?.querySelectorAll('.theme-svg-background, .theme-new-layer, .theme-old-layer');
if (existingBgs) {
existingBgs.forEach(bg => bg.remove());
}
// 设置初始状态的背景(与硬币的正面状态对应)
setSVGBackground();
// 添加更新按钮样式的过程
setTimeout(() => {
// 根据当前主题ID更新按钮样式
if (panelContainer) {
const buttons = panelContainer.querySelectorAll('.layout-btn');
const btnOpacity = parseFloat(panelContainer.dataset.btnOpacity || DEFAULT_OPACITY.buttons);
buttons.forEach(button => {
// 根据当前主题设置按钮背景颜色
const bgColor = currentThemeId === THEME_ID.PURPLE
? `rgba(106, 90, 205, ${btnOpacity})` // 秩序主题 - 紫色
: `rgba(170, 0, 0, ${btnOpacity})`; // 混沌主题 - 红色
button.style.backgroundColor = bgColor;
});
}
}, 100);
return true;
},
// 翻转硬币
flipCoin() {
flipCoin();
return true;
},
// 设置面板透明度
setPanelOpacity(opacity) {
setPanelOpacity(opacity);
return true;
},
// 获取面板透明度
getPanelOpacity() {
return currentPanelOpacity;
},
// 获取当前主题样式ID
getCurrentThemeId() {
return currentThemeId;
},
// 清理主题资源
cleanup() {
removeStyles('ancient-god-theme-css');
removeStyles('ancient-god-transition-css');
if (panelContainer) {
// 移除所有类型的背景层
const backgrounds = panelContainer.querySelectorAll('.theme-svg-background, .theme-old-layer, .theme-new-layer');
backgrounds.forEach(bg => bg.remove());
// 移除可能存在的扩散效果容器
const rippleContainer = panelContainer.querySelector('.ancient-god-ripple-container');
if (rippleContainer) {
rippleContainer.remove();
}
}
return true;
}
};
export default AncientGodTheme;
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long