Skip to content

Commit 9f942bb

Browse files
committed
修复标签弹窗无滚动条问题,优化小助手ui,实现自动避开滚动条
1 parent 9110ada commit 9f942bb

5 files changed

Lines changed: 346 additions & 71 deletions

File tree

js/css/assistant.css

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
backdrop-filter: blur(4px);
2323
transform-origin: bottom right;
2424
user-select: none;
25-
will-change: width, opacity, background-color, border-color, backdrop-filter, box-shadow;
25+
will-change: width, opacity, background-color, border-color, backdrop-filter, box-shadow, right, left;
2626
width: auto;
2727
height: 26px !important;
2828
max-width: fit-content;
@@ -97,10 +97,10 @@
9797
/* 悬停区域 - 用于检测鼠标悬停 */
9898
.prompt-assistant-hover-area {
9999
position: absolute;
100-
top: 0;
101-
right: 6px;
102-
width: 16px;
103-
height: 100%;
100+
top: 2px;
101+
right: 2px;
102+
width: 80%;
103+
height: 90%;
104104
z-index: 1;
105105
/* 降低z-index,确保在按钮下方 */
106106
cursor: pointer;
@@ -278,6 +278,18 @@
278278
border-radius: 0.5px;
279279
}
280280

281+
/* 过渡效果类 */
282+
.prompt-assistant-transition {
283+
transition: width 0.3s cubic-bezier(0.25, 1, 0.5, 1),
284+
opacity 0.3s cubic-bezier(0.25, 1, 0.5, 1),
285+
border-color 0.5s ease-out,
286+
backdrop-filter 0.5s ease-out,
287+
box-shadow 0.2s ease-out,
288+
background-color 0.5s ease-out,
289+
right 0.2s ease-out,
290+
left 0.2s ease-out;
291+
}
292+
281293
/* 显示动画类 */
282294
.assistant-show {
283295
display: flex !important;

js/css/popup.css

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
1616
min-width: 600px;
1717
max-width: 600px;
18-
max-height: 400px;
18+
min-height: 200px;
19+
max-height: 80vh; /* 使用视窗高度的80%作为最大高度 */
1920
overflow: hidden;
2021
display: flex;
2122
flex-direction: column;
@@ -236,12 +237,12 @@
236237

237238
/* 弹窗内容区域 */
238239
.popup_content {
239-
max-height: 300px;
240+
flex: 1;
240241
overflow-y: auto;
242+
overflow-x: hidden;
241243
scrollbar-width: thin;
242244
scrollbar-color: color-mix(in srgb, var(--p-button-text-secondary-color), transparent 50%) transparent;
243-
flex: 1;
244-
245+
min-height: 0; /* 允许flex收缩 */
245246
}
246247

247248
.popup_content::-webkit-scrollbar {
@@ -419,13 +420,12 @@
419420
}
420421

421422
.tag_accordion_header:hover {
422-
background-color: color-mix(in srgb, var(--p-primary-500), transparent 84%);
423+
background-color: color-mix(in srgb, var(--p-surface-400), transparent 80%);
423424

424425
}
425426

426427
.tag_accordion_header.active {
427-
background-color: color-mix(in srgb, var(--comfy-menu-secondary-bg), transparent 50%);
428-
;
428+
background-color: color-mix(in srgb, var(--p-primary-400), transparent 85%);
429429
border-bottom-left-radius: 0;
430430
border-bottom-right-radius: 0;
431431
}
@@ -452,14 +452,15 @@
452452
/* 添加手风琴图标尺寸控制 */
453453
.tag_accordion_icon .pi {
454454
font-size: 12px;
455+
transition: transform 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
455456
}
456457

457458
.tag_accordion_icon img,
458459
.tag_accordion_icon .svg-icon,
459460
.accordion_arrow_icon {
460461
width: 10px;
461462
height: 10px;
462-
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
463+
transition: transform 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
463464
}
464465

465466
.tag_accordion_icon img,
@@ -475,25 +476,42 @@
475476
overflow: hidden;
476477
cursor: default;
477478
/* 确保内容区域使用默认指针 */
478-
transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1), padding 0.3s cubic-bezier(0.4, 0, 0.2, 1);
479+
/* 移除CSS过渡,改为JavaScript控制动画 */
479480
}
480481

481482
.tag_accordion_content.active {
482483
display: block;
483-
max-height: 200px;
484+
max-height: none; /* 移除高度限制,让内容自适应 */
484485
padding: 2px 0;
485-
overflow-y: auto;
486+
overflow: visible; /* 移除滚动条,让父容器处理滚动 */
486487
cursor: default;
487488
/* 确保激活状态下内容区域也使用默认指针 */
488489
}
489490

491+
/* 优化手风琴动画性能 */
492+
.tag_accordion_content {
493+
will-change: max-height, padding; /* 提示浏览器优化这些属性的动画 */
494+
transform: translateZ(0); /* 启用硬件加速 */
495+
backface-visibility: hidden; /* 避免背面可见性问题 */
496+
}
497+
498+
/* 旋转图标优化 */
499+
.rotate-180 {
500+
transform: rotate(180deg);
501+
}
502+
503+
.pi.rotate-180,
504+
.accordion_arrow_icon.rotate-180 {
505+
transform: rotate(180deg);
506+
}
507+
490508
/* 标签容器样式 */
491509
.tag_category_container {
492-
max-height: 300px;
493-
overflow-y: auto;
510+
flex: 1;
511+
overflow: visible; /* 移除滚动条,让父容器处理滚动 */
494512
padding: 4px;
495513
position: relative;
496-
flex: 1;
514+
min-height: 0; /* 允许flex收缩 */
497515
}
498516

499517
/* 标签项样式 */
@@ -724,11 +742,13 @@
724742
/* Tab内容容器 */
725743
.popup_container .popup_tab_content {
726744
display: none;
727-
max-height: 290px;
745+
flex: 1;
728746
overflow-y: auto;
747+
overflow-x: hidden;
729748
scrollbar-width: thin;
730749
scrollbar-color: color-mix(in srgb, var(--p-button-text-secondary-color), transparent 50%) transparent;
731750
padding-bottom: 8px;
751+
min-height: 0; /* 允许flex收缩 */
732752
}
733753

734754
.popup_container .popup_tab_content::-webkit-scrollbar {
@@ -745,7 +765,8 @@
745765
}
746766

747767
.popup_container .popup_tab_content.active {
748-
display: block;
768+
display: flex;
769+
flex-direction: column;
749770
}
750771

751772
/* 滚动指示器 - PrimeVue 风格 */
@@ -893,17 +914,25 @@
893914

894915
/* 标签弹窗的内容区域适应动态高度 */
895916
.popup_container.tag_popup .popup_tab_content {
896-
max-height: none;
897-
/* 移除固定高度限制 */
898-
height: calc(100% - 50px);
899-
/* 动态计算高度,减去标题栏和标签栏的高度 */
917+
flex: 1;
918+
min-height: 0; /* 允许flex收缩 */
919+
overflow-y: auto; /* 只有标签页内容容器有滚动条 */
920+
overflow-x: hidden;
900921
}
901922

902923
.popup_container.tag_popup .tag_category_container {
903-
max-height: none;
904-
/* 移除固定高度限制 */
905-
height: 100%;
906-
/* 占满可用高度 */
924+
flex: 1;
925+
min-height: 0; /* 允许flex收缩 */
926+
overflow: visible; /* 移除滚动条,让父容器处理 */
927+
}
928+
929+
/* 确保手风琴内容不产生滚动条 */
930+
.popup_container.tag_popup .tag_accordion_content {
931+
overflow: visible !important;
932+
}
933+
934+
.popup_container.tag_popup .tag_accordion_content.active {
935+
overflow: visible !important;
907936
}
908937

909938
.popup_container.tag_popup .tag_search_result_list {

js/modules/PromptAssistant.js

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -635,11 +635,38 @@ class PromptAssistant {
635635
widget._eventCleanupFunctions = widget._eventCleanupFunctions || [];
636636
widget._eventCleanupFunctions.push(removeBlurListener);
637637

638-
// 添加输入事件监听,实时更新撤销/重做按钮状态
638+
// 添加输入事件监听,实时更新撤销/重做按钮状态和位置调整
639639
const removeInputListener = EventManager.addDOMListener(inputWidget.inputEl, 'input', () => {
640640
UIToolkit.updateUndoRedoButtonState(widget, HistoryCacheService);
641+
// 检测滚动条状态并调整位置
642+
this._adjustPositionForScrollbar(widget, inputWidget.inputEl);
641643
});
642644
widget._eventCleanupFunctions.push(removeInputListener);
645+
646+
// 添加ResizeObserver监听输入框尺寸变化
647+
if (window.ResizeObserver) {
648+
const resizeObserver = new ResizeObserver(() => {
649+
// 延迟执行,确保浏览器完成布局更新
650+
setTimeout(() => {
651+
this._adjustPositionForScrollbar(widget, inputWidget.inputEl);
652+
}, 10);
653+
});
654+
655+
resizeObserver.observe(inputWidget.inputEl);
656+
657+
// 添加清理函数
658+
widget._eventCleanupFunctions.push(() => {
659+
resizeObserver.disconnect();
660+
});
661+
} else {
662+
// 降级方案:监听window resize事件
663+
const removeResizeListener = EventManager.addDOMListener(window, 'resize',
664+
EventManager.debounce(() => {
665+
this._adjustPositionForScrollbar(widget, inputWidget.inputEl);
666+
}, 100)
667+
);
668+
widget._eventCleanupFunctions.push(removeResizeListener);
669+
}
643670
}
644671

645672
return widget;
@@ -705,6 +732,11 @@ class PromptAssistant {
705732
this._setupUIEventHandling(widget, inputEl, containerDiv);
706733
this._setupUIPosition(widget, inputEl, containerDiv, canvasContainerRect);
707734

735+
// 初始滚动条检测和位置调整
736+
setTimeout(() => {
737+
this._adjustPositionForScrollbar(widget, inputEl);
738+
}, 100); // 延迟执行,确保DOM完全渲染
739+
708740
// 默认隐藏状态
709741
containerDiv.style.display = 'none';
710742

@@ -1967,6 +1999,65 @@ class PromptAssistant {
19671999
return button;
19682000
}
19692001

2002+
/**
2003+
* 检测输入框是否有滚动条
2004+
* @param {HTMLElement} inputEl - 输入框元素
2005+
* @returns {boolean} 是否有垂直滚动条
2006+
*/
2007+
_detectScrollbar(inputEl) {
2008+
if (!inputEl || inputEl.tagName !== 'TEXTAREA') {
2009+
return false;
2010+
}
2011+
2012+
try {
2013+
// 检查垂直滚动条:scrollHeight > clientHeight
2014+
const hasVerticalScrollbar = inputEl.scrollHeight > inputEl.clientHeight;
2015+
logger.debug(`[滚动条检测] 输入框滚动状态 | scrollHeight: ${inputEl.scrollHeight} | clientHeight: ${inputEl.clientHeight} | 有滚动条: ${hasVerticalScrollbar}`);
2016+
return hasVerticalScrollbar;
2017+
} catch (error) {
2018+
logger.error(`[滚动条检测] 检测失败 | 错误: ${error.message}`);
2019+
return false;
2020+
}
2021+
}
2022+
2023+
/**
2024+
* 根据滚动条状态调整小助手位置
2025+
* @param {Object} widget - 小助手实例
2026+
* @param {HTMLElement} inputEl - 输入框元素
2027+
*/
2028+
_adjustPositionForScrollbar(widget, inputEl) {
2029+
if (!widget?.element || !inputEl) return;
2030+
2031+
const hasScrollbar = this._detectScrollbar(inputEl);
2032+
const containerDiv = widget.element;
2033+
2034+
// 检查当前定位模式
2035+
const isStandardMode = containerDiv.style.position !== 'fixed';
2036+
2037+
if (isStandardMode) {
2038+
// ---标准定位模式调整---
2039+
const rightOffset = hasScrollbar ? '16px' : '4px'; // 有滚动条时向左偏移10px
2040+
containerDiv.style.right = rightOffset;
2041+
logger.debug(`[位置调整] 标准方案 | 有滚动条: ${hasScrollbar} | 右偏移: ${rightOffset}`);
2042+
} else {
2043+
// ---兼容定位模式调整---
2044+
// 需要重新计算位置,因为固定定位使用的是left属性
2045+
const inputRect = inputEl.getBoundingClientRect();
2046+
const offsetRight = hasScrollbar ? 18 : 6; // 有滚动条时增加10px偏移
2047+
2048+
// 临时隐藏以获取正确尺寸
2049+
containerDiv.style.visibility = 'hidden';
2050+
void containerDiv.offsetWidth;
2051+
2052+
// 计算新位置
2053+
const newLeft = inputRect.right - containerDiv.offsetWidth - offsetRight;
2054+
containerDiv.style.left = `${newLeft}px`;
2055+
containerDiv.style.visibility = 'visible';
2056+
2057+
logger.debug(`[位置调整] 兼容方案 | 有滚动条: ${hasScrollbar} | 左偏移: ${newLeft}px`);
2058+
}
2059+
}
2060+
19702061
/**
19712062
* 设置UI位置
19722063
* 使用绝对定位方式
@@ -2116,8 +2207,9 @@ class PromptAssistant {
21162207
// 强制回流以获取正确尺寸
21172208
void containerDiv.offsetWidth;
21182209

2119-
// 现在设置位置 - 放置在输入框右下角
2120-
const offsetRight = 6; // 右侧偏移
2210+
// 现在设置位置 - 放置在输入框右下角,考虑滚动条状态
2211+
const hasScrollbar = inputEl.tagName === 'TEXTAREA' && inputEl.scrollHeight > inputEl.clientHeight;
2212+
const offsetRight = hasScrollbar ? 16 : 6; // 有滚动条时增加10px偏移
21212213
const offsetBottom = 4;
21222214

21232215
containerDiv.style.left = `${inputRect.right - containerDiv.offsetWidth - offsetRight}px`;

0 commit comments

Comments
 (0)