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 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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user