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 closeAllTabs = useTabStore(s => s.closeAllTabs)
const closeTabsToRight = useTabStore(s => s.closeTabsToRight) const closeTabsToRight = useTabStore(s => s.closeTabsToRight)
const tabBarRef = useRef<HTMLDivElement>(null)
const tabListRef = useRef<HTMLDivElement>(null) const tabListRef = useRef<HTMLDivElement>(null)
const [menu, setMenu] = useState<ContextMenuState>({ visible: false, x: 0, y: 0, tabId: '' }) const [menu, setMenu] = useState<ContextMenuState>({ visible: false, x: 0, y: 0, tabId: '' })
// 支持鼠标滚轮水平滚动标签 // 滚动到活动标签
useEffect(() => { const scrollToActiveTab = useCallback(() => {
const tabBar = tabBarRef.current const tabList = tabListRef.current
if (!tabBar) return if (!tabList) return
const activeTab = tabList.querySelector('.tab-item.active') as HTMLElement
if (!activeTab) return
const handleWheel = (e: WheelEvent) => { const listRect = tabList.getBoundingClientRect()
const tabList = tabListRef.current const tabRect = activeTab.getBoundingClientRect()
if (!tabList) return
// 只有当标签列表有水平滚动空间时才处理 // 如果标签在可视区域左侧之外
const hasHorizontalScroll = tabList.scrollWidth > tabList.clientWidth if (tabRect.left < listRect.left) {
if (!hasHorizontalScroll) return tabList.scrollLeft -= (listRect.left - tabRect.left + 20)
}
// 阻止默认的垂直滚动 // 如果标签在可视区域右侧之外
e.preventDefault() else if (tabRect.right > listRect.right) {
e.stopPropagation() tabList.scrollLeft += (tabRect.right - listRect.right + 20)
// 计算滚动量:优先使用 deltaX(触控板左右滑动),否则用 deltaY(鼠标滚轮)
const scrollAmount = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : e.deltaY
// 应用滚动
tabList.scrollLeft += scrollAmount
} }
// 绑定到 #tab-bar 上,确保能捕获所有滚轮事件
tabBar.addEventListener('wheel', handleWheel, { passive: false })
return () => tabBar.removeEventListener('wheel', handleWheel)
}, []) }, [])
// 自动滚动到活动标签 // 自动滚动到活动标签
useEffect(() => { useEffect(() => {
if (!tabListRef.current || !activeTabId) return requestAnimationFrame(scrollToActiveTab)
}, [activeTabId, scrollToActiveTab])
// 使用 requestAnimationFrame 确保 DOM 已更新
requestAnimationFrame(() => { // 支持鼠标滚轮水平滚动标签栏
const activeTab = tabListRef.current?.querySelector('.tab-item.active') useEffect(() => {
if (activeTab) { const tabList = tabListRef.current
activeTab.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' }) if (!tabList) return
}
}) const handleWheel = (e: WheelEvent) => {
}, [activeTabId]) // 检查是否有水平溢出
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) => { const handleClose = useCallback((e: React.MouseEvent, tabId: string) => {
e.stopPropagation() e.stopPropagation()
@@ -144,7 +148,7 @@ export function TabBar() {
if (tabs.length === 0) return null if (tabs.length === 0) return null
return ( return (
<div id="tab-bar" ref={tabBarRef}> <div id="tab-bar">
<div id="tab-list" ref={tabListRef}> <div id="tab-list" ref={tabListRef}>
{tabs.map(tab => ( {tabs.map(tab => (
<div <div
+7 -8
View File
@@ -470,7 +470,7 @@ html, body {
align-items: flex-end; align-items: flex-end;
padding: 0 4px; padding: 0 4px;
flex-shrink: 0; flex-shrink: 0;
/* 不设置 overflow,由 #tab-list 负责滚动 */ overflow: hidden;
} }
#tab-list { #tab-list {
@@ -481,26 +481,25 @@ html, body {
min-width: 0; min-width: 0;
overflow-x: auto; overflow-x: auto;
overflow-y: hidden; overflow-y: hidden;
/* 平滑滚动 */ scroll-behavior: auto;
scroll-behavior: smooth;
} }
/* 标签列表滚动条 */ /* 滚动条样式 */
#tab-list::-webkit-scrollbar { #tab-list::-webkit-scrollbar {
height: 3px; height: 4px;
} }
#tab-list::-webkit-scrollbar-thumb { #tab-list::-webkit-scrollbar-thumb {
background: var(--border); background: var(--text-tertiary);
border-radius: 2px; border-radius: 2px;
} }
#tab-list::-webkit-scrollbar-thumb:hover { #tab-list::-webkit-scrollbar-thumb:hover {
background: var(--text-tertiary); background: var(--text-secondary);
} }
#tab-list::-webkit-scrollbar-track { #tab-list::-webkit-scrollbar-track {
background: transparent; background: var(--bg-tertiary);
} }
#tab-list::-webkit-scrollbar-thumb:hover { #tab-list::-webkit-scrollbar-thumb:hover {