fix(ui): TabBar 点击滑块从旧位置滑到新位置
- 点击时不再让滑块在 touchstart 直接跳到手指位置 - 拖动超过 12px 阈值后才跟手,位置统一用百分比,避免 px/calc 混用导致跳变 - 松手按命中 tab 切页,同一 tab 不重复跳转
This commit is contained in:
@@ -5,7 +5,7 @@ import { getTheme, resolveTheme, setDetectedSystemTheme } from '../utils/store'
|
||||
import { THEME_CHANGE_EVENT } from '../context/ThemeContext'
|
||||
import { applyPageBackground } from '../utils/themeBackground'
|
||||
import { assetUrl } from '../utils/asset'
|
||||
import { clampDragX, getTabBarFrame, tabIndexAtX } from './tabBarGeometry'
|
||||
import { clampPercent, getTabBarFrame, hasDragged, tabIndexAtPercent } from './tabBarGeometry'
|
||||
import './index.scss'
|
||||
|
||||
const TABS = [
|
||||
@@ -17,6 +17,7 @@ const TABS = [
|
||||
]
|
||||
|
||||
const TAB_CENTER_STEP = 100 / TABS.length
|
||||
const DRAG_THRESHOLD_PX = 12
|
||||
|
||||
function matchTab(path: string): string {
|
||||
const idx = TABS.findIndex((t) => path.endsWith(t.pagePath))
|
||||
@@ -39,8 +40,9 @@ export default function CustomTabBar() {
|
||||
|
||||
// 微信自定义 tabBar 不会自动感知 page 路由变化,用 state 保持选中态响应式
|
||||
const [activeTab, setActiveTab] = useState<string>(currentTabPath)
|
||||
const [dragX, setDragX] = useState<number | null>(null)
|
||||
const dragXRef = useRef<number | null>(null)
|
||||
const [dragPercent, setDragPercent] = useState<number | null>(null)
|
||||
const dragPercentRef = useRef<number | null>(null)
|
||||
const startXRef = useRef<number>(0)
|
||||
const barFrameRef = useRef<{ left: number; width: number } | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -102,38 +104,47 @@ export default function CustomTabBar() {
|
||||
if (!touch) return
|
||||
const frame = getTabBarFrame(Taro.getSystemInfoSync().windowWidth)
|
||||
barFrameRef.current = frame
|
||||
const nextX = clampDragX(touch.clientX - frame.left, frame.width)
|
||||
dragXRef.current = nextX
|
||||
setDragX(nextX)
|
||||
startXRef.current = touch.clientX
|
||||
dragPercentRef.current = null
|
||||
setDragPercent(null)
|
||||
}
|
||||
|
||||
const moveDrag = (e: any) => {
|
||||
if (dragXRef.current === null) return
|
||||
const frame = barFrameRef.current
|
||||
const touch = e.touches && e.touches[0]
|
||||
if (!frame || !touch) return
|
||||
const nextX = clampDragX(touch.clientX - frame.left, frame.width)
|
||||
dragXRef.current = nextX
|
||||
setDragX(nextX)
|
||||
if (dragPercentRef.current === null && !hasDragged(startXRef.current, touch.clientX, DRAG_THRESHOLD_PX)) {
|
||||
return
|
||||
}
|
||||
const nextPercent = clampPercent(((touch.clientX - frame.left) / frame.width) * 100)
|
||||
dragPercentRef.current = nextPercent
|
||||
setDragPercent(nextPercent)
|
||||
}
|
||||
|
||||
const endDrag = () => {
|
||||
const endDrag = (e: any) => {
|
||||
const frame = barFrameRef.current
|
||||
const x = dragXRef.current
|
||||
if (frame && x !== null) {
|
||||
const idx = tabIndexAtX(x, frame.width, TABS.length)
|
||||
const target = TABS[idx]
|
||||
if (target && target.pagePath !== activeTab) switchTab(target.pagePath)
|
||||
if (!frame) return
|
||||
let targetIndex: number
|
||||
if (dragPercentRef.current !== null) {
|
||||
targetIndex = tabIndexAtPercent(dragPercentRef.current, TABS.length)
|
||||
} else {
|
||||
const touch = e.changedTouches && e.changedTouches[0]
|
||||
const x = touch ? touch.clientX : startXRef.current
|
||||
targetIndex = tabIndexAtPercent(clampPercent(((x - frame.left) / frame.width) * 100), TABS.length)
|
||||
}
|
||||
const target = TABS[targetIndex]
|
||||
if (target && target.pagePath !== activeTab) switchTab(target.pagePath)
|
||||
barFrameRef.current = null
|
||||
dragXRef.current = null
|
||||
setDragX(null)
|
||||
startXRef.current = 0
|
||||
dragPercentRef.current = null
|
||||
setDragPercent(null)
|
||||
}
|
||||
|
||||
const cancelDrag = () => {
|
||||
barFrameRef.current = null
|
||||
dragXRef.current = null
|
||||
setDragX(null)
|
||||
startXRef.current = 0
|
||||
dragPercentRef.current = null
|
||||
setDragPercent(null)
|
||||
}
|
||||
|
||||
const activeIndex = Math.max(0, TABS.findIndex((t) => t.pagePath === activeTab))
|
||||
@@ -152,8 +163,8 @@ export default function CustomTabBar() {
|
||||
<View className='tab-bar-caustic' />
|
||||
<View className='tab-bar-rim' />
|
||||
<View
|
||||
className={`tab-indicator ${dragX !== null ? 'dragging' : ''}`}
|
||||
style={{ left: dragX === null ? `calc(${(activeIndex + 0.5) * TAB_CENTER_STEP}%)` : `${dragX}px` }}
|
||||
className={`tab-indicator ${dragPercent !== null ? 'dragging' : ''}`}
|
||||
style={{ left: dragPercent === null ? `${(activeIndex + 0.5) * TAB_CENTER_STEP}%` : `${dragPercent}%` }}
|
||||
/>
|
||||
{TABS.map((tab) => {
|
||||
const isActive = tab.pagePath === activeTab
|
||||
|
||||
@@ -3,16 +3,17 @@ export function clamp(value, min, max) {
|
||||
return Math.min(max, Math.max(min, value))
|
||||
}
|
||||
|
||||
export function clampDragX(x, barWidth) {
|
||||
return clamp(x, 0, Math.max(0, barWidth))
|
||||
export function clampPercent(value) {
|
||||
return clamp(value, 0, 100)
|
||||
}
|
||||
|
||||
export function tabIndexAtX(x, barWidth, tabCount) {
|
||||
if (!Number.isFinite(x) || !Number.isFinite(barWidth) || barWidth <= 0 || tabCount <= 0) {
|
||||
return 0
|
||||
}
|
||||
const ratio = clampDragX(x, barWidth) / barWidth
|
||||
return clamp(Math.floor(ratio * tabCount), 0, tabCount - 1)
|
||||
export function tabIndexAtPercent(percent, tabCount) {
|
||||
if (!Number.isFinite(percent) || tabCount <= 0) return 0
|
||||
return clamp(Math.floor((clampPercent(percent) / 100) * tabCount), 0, tabCount - 1)
|
||||
}
|
||||
|
||||
export function hasDragged(startX, currentX, threshold) {
|
||||
return Math.abs(currentX - startX) >= threshold
|
||||
}
|
||||
|
||||
export function getTabBarFrame(windowWidth) {
|
||||
|
||||
Reference in New Issue
Block a user