fix: 标签栏滚轮滚动重写
- wheel 事件直接绑定到 #tab-list 元素(而非父容器) - 支持触控板 deltaX 和鼠标滚轮 deltaY - 自动滚动到活动标签改为手动计算位置(不依赖 scrollIntoView) - CSS: #tab-bar 添加 overflow: hidden 防止溢出 - CSS: 滚动条更明显(4px 高,灰色轨道)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user