feat(ui): 底栏可拖动切换 + 透明度降至 0.24

- 滑块随手指实时移动,松手后跳转到目标 tab
- 抽出 tabBarGeometry 计算拖拽命中,配套单测
- 底栏浅色/深色底色 0.32 -> 0.24
This commit is contained in:
2026-08-18 22:54:28 +08:00
parent f9099f4aec
commit 9bd7d0421f
7 changed files with 119 additions and 15 deletions
+61 -8
View File
@@ -1,10 +1,11 @@
import { View, Text, Image } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { useEffect, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
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 './index.scss'
const TABS = [
@@ -15,6 +16,8 @@ const TABS = [
{ pagePath: '/pages/profile/index', label: '我的', icon: assetUrl('/icon/个人.svg'), iconActive: assetUrl('/icon/个人-fill.svg') }
]
const TAB_CENTER_STEP = 100 / TABS.length
function matchTab(path: string): string {
const idx = TABS.findIndex((t) => path.endsWith(t.pagePath))
return TABS[idx >= 0 ? idx : 0].pagePath
@@ -36,6 +39,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 barFrameRef = useRef<{ left: number; width: number } | null>(null)
useEffect(() => {
const handler = (t: 'light' | 'dark') => setResolvedTheme(t)
@@ -91,21 +97,68 @@ export default function CustomTabBar() {
Taro.eventCenter.trigger('tabBarChange', url)
}
const startDrag = (e: any) => {
const touch = e.touches && e.touches[0]
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)
}
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)
}
const endDrag = () => {
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)
}
barFrameRef.current = null
dragXRef.current = null
setDragX(null)
}
const cancelDrag = () => {
barFrameRef.current = null
dragXRef.current = null
setDragX(null)
}
const activeIndex = Math.max(0, TABS.findIndex((t) => t.pagePath === activeTab))
return (
<View className={`tab-bar-root theme-${resolvedTheme}`}>
<View className='tab-bar-gradient' />
<View className={`custom-tab-bar theme-${resolvedTheme}`}>
<View
className={`custom-tab-bar theme-${resolvedTheme}`}
onTouchStart={startDrag}
onTouchMove={moveDrag}
onTouchEnd={endDrag}
onTouchCancel={cancelDrag}
catchMove
>
<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` }}
/>
{TABS.map((tab) => {
const isActive = tab.pagePath === activeTab
return (
<View
key={tab.pagePath}
className={`tab-item ${isActive ? 'active' : ''}`}
onTap={() => switchTab(tab.pagePath)}
>
{isActive && <View className='tab-indicator' />}
<View key={tab.pagePath} className={`tab-item ${isActive ? 'active' : ''}`}>
<Image className={`tab-icon-img ${isActive ? 'active' : ''}`} src={isActive ? tab.iconActive : tab.icon} mode='aspectFit' />
<Text className='tab-label'>{tab.label}</Text>
</View>