fix: 标签栏滚轮滚动重写

- wheel 事件直接绑定到 #tab-list 元素(而非父容器)
- 支持触控板 deltaX 和鼠标滚轮 deltaY
- 自动滚动到活动标签改为手动计算位置(不依赖 scrollIntoView)
- CSS: #tab-bar 添加 overflow: hidden 防止溢出
- CSS: 滚动条更明显(4px 高,灰色轨道)
This commit is contained in:
thzxx
2026-05-28 15:10:46 +08:00
parent 531e4bdbc3
commit 3294dd585c
2 changed files with 47 additions and 44 deletions
+40 -36
View File
@@ -20,51 +20,55 @@ export function TabBar() {
const closeAllTabs = useTabStore(s => s.closeAllTabs)
const closeTabsToRight = useTabStore(s => s.closeTabsToRight)
const tabBarRef = useRef<HTMLDivElement>(null)
const tabListRef = useRef<HTMLDivElement>(null)
const [menu, setMenu] = useState<ContextMenuState>({ visible: false, x: 0, y: 0, tabId: '' })
// 支持鼠标滚轮水平滚动标签
useEffect(() => {
const tabBar = tabBarRef.current
if (!tabBar) return
// 滚动到活动标签
const scrollToActiveTab = useCallback(() => {
const tabList = tabListRef.current
if (!tabList) return
const activeTab = tabList.querySelector('.tab-item.active') as HTMLElement
if (!activeTab) return
const handleWheel = (e: WheelEvent) => {
const tabList = tabListRef.current
if (!tabList) return
const listRect = tabList.getBoundingClientRect()
const tabRect = activeTab.getBoundingClientRect()
// 只有当标签列表有水平滚动空间时才处理
const hasHorizontalScroll = tabList.scrollWidth > tabList.clientWidth
if (!hasHorizontalScroll) return
// 阻止默认的垂直滚动
e.preventDefault()
e.stopPropagation()
// 计算滚动量:优先使用 deltaX(触控板左右滑动),否则用 deltaY(鼠标滚轮)
const scrollAmount = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : e.deltaY
// 应用滚动
tabList.scrollLeft += scrollAmount
// 如果标签在可视区域左侧之外
if (tabRect.left < listRect.left) {
tabList.scrollLeft -= (listRect.left - tabRect.left + 20)
}
// 如果标签在可视区域右侧之外
else if (tabRect.right > listRect.right) {
tabList.scrollLeft += (tabRect.right - listRect.right + 20)
}
// 绑定到 #tab-bar 上,确保能捕获所有滚轮事件
tabBar.addEventListener('wheel', handleWheel, { passive: false })
return () => tabBar.removeEventListener('wheel', handleWheel)
}, [])
// 自动滚动到活动标签
useEffect(() => {
if (!tabListRef.current || !activeTabId) return
// 使用 requestAnimationFrame 确保 DOM 已更新
requestAnimationFrame(() => {
const activeTab = tabListRef.current?.querySelector('.tab-item.active')
if (activeTab) {
activeTab.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' })
}
})
}, [activeTabId])
requestAnimationFrame(scrollToActiveTab)
}, [activeTabId, scrollToActiveTab])
// 支持鼠标滚轮水平滚动标签栏
useEffect(() => {
const tabList = tabListRef.current
if (!tabList) return
const handleWheel = (e: WheelEvent) => {
// 检查是否有水平溢出
if (tabList.scrollWidth <= tabList.clientWidth) return
// 阻止默认滚动
e.preventDefault()
// 计算滚动量:支持触控板 deltaX 和鼠标滚轮 deltaY
const delta = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : e.deltaY
tabList.scrollLeft += delta
}
// 直接绑定到 tabList,使用 passive: false 允许 preventDefault
tabList.addEventListener('wheel', handleWheel, { passive: false })
return () => tabList.removeEventListener('wheel', handleWheel)
}, [])
const handleClose = useCallback((e: React.MouseEvent, tabId: string) => {
e.stopPropagation()
@@ -144,7 +148,7 @@ export function TabBar() {
if (tabs.length === 0) return null
return (
<div id="tab-bar" ref={tabBarRef}>
<div id="tab-bar">
<div id="tab-list" ref={tabListRef}>
{tabs.map(tab => (
<div
+7 -8
View File
@@ -470,7 +470,7 @@ html, body {
align-items: flex-end;
padding: 0 4px;
flex-shrink: 0;
/* 不设置 overflow,由 #tab-list 负责滚动 */
overflow: hidden;
}
#tab-list {
@@ -481,26 +481,25 @@ html, body {
min-width: 0;
overflow-x: auto;
overflow-y: hidden;
/* 平滑滚动 */
scroll-behavior: smooth;
scroll-behavior: auto;
}
/* 标签列表滚动条 */
/* 滚动条样式 */
#tab-list::-webkit-scrollbar {
height: 3px;
height: 4px;
}
#tab-list::-webkit-scrollbar-thumb {
background: var(--border);
background: var(--text-tertiary);
border-radius: 2px;
}
#tab-list::-webkit-scrollbar-thumb:hover {
background: var(--text-tertiary);
background: var(--text-secondary);
}
#tab-list::-webkit-scrollbar-track {
background: transparent;
background: var(--bg-tertiary);
}
#tab-list::-webkit-scrollbar-thumb:hover {