React中如何实现点击事件跳转但触摸事件不跳转?
解决React导航组件的点击/触摸行为区分问题
Hey there! Great question—this is a super common scenario when building responsive navigation, so let's break this down step by step to get exactly the behavior you want: desktop hover expands submenus, click navigates; touch on mobile only expands submenus without immediate navigation.
核心思路
We need to:
- 追踪交互是否由触摸触发(避免移动端误触跳转)
- 为桌面端单独处理悬停展开逻辑
- 用状态控制子菜单的显示/隐藏
- 在触摸触发的"点击"事件中阻止页面跳转
具体实现代码示例
这里是一个基于react-router-dom和react-popper的完整函数组件示例:
import { useState, useRef } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { Popper, Reference } from 'react-popper'; const TopNavItem = ({ mainPath, mainLabel, submenuItems }) => { // 控制子菜单显示状态 const [isSubmenuOpen, setIsSubmenuOpen] = useState(false); // 记录是否为触摸触发的交互(解决移动端触摸后延迟触发点击的问题) const isTouchTriggered = useRef(false); const navigate = useNavigate(); // react-popper 需要的主菜单项引用 const navItemRef = useRef(null); // 处理触摸开始事件:切换子菜单状态,标记为触摸触发 const handleTouchStart = (e) => { e.preventDefault(); // 阻止默认触摸行为(避免误触发跳转) isTouchTriggered.current = true; setIsSubmenuOpen(prev => !prev); // 切换子菜单显示状态 }; // 处理点击事件:仅当非触摸触发时才允许跳转 const handleClick = (e) => { if (isTouchTriggered.current) { // 该点击由触摸交互触发,不执行跳转 e.preventDefault(); isTouchTriggered.current = false; // 重置标记,为下一次交互做准备 return; } // 桌面端点击时,让Link正常跳转;如果需要自定义跳转逻辑,可使用navigate() // navigate(mainPath); // 若不直接使用Link组件,可取消注释此代码 }; // 桌面端悬停行为 const handleMouseEnter = () => { setIsSubmenuOpen(true); }; const handleMouseLeave = () => { setIsSubmenuOpen(false); }; return ( <div ref={navItemRef}> <Reference> {({ ref }) => ( <Link to={mainPath} ref={ref} onClick={handleClick} onTouchStart={handleTouchStart} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} className="main-nav-item" > {mainLabel} </Link> )} </Reference> {/* 基于react-popper渲染的子菜单 */} {isSubmenuOpen && ( <Popper placement="bottom-start" referenceElement={navItemRef.current}> {({ ref, style, placement }) => ( <div ref={ref} style={style} data-placement={placement} className="submenu" > {submenuItems.map((item) => ( <Link key={item.path} to={item.path} className="submenu-item"> {item.label} </Link> ))} </div> )} </Popper> )} </div> ); }; export default TopNavItem;
关键细节解释
- 触摸状态追踪:使用
useRef(isTouchTriggered)标记交互是否由触摸发起,因为移动端浏览器会在触摸后约300ms触发点击事件,这样可以避免延迟的点击触发跳转。 - 触摸事件处理:
handleTouchStart函数切换子菜单状态并阻止默认触摸行为(避免触发Link的跳转逻辑)。 - 点击事件处理:点击处理器会检查是否为触摸触发的交互,如果是则阻止跳转并重置标记;桌面端点击则让Link正常跳转。
- 悬停行为:简单的鼠标进入/离开事件处理器,为桌面用户控制子菜单的显示与隐藏。
额外注意事项
- 样式优化:添加CSS样式美化子菜单(如定位、背景、过渡动画),提升交互体验。
- Popper配置:确保
referenceElement正确关联到主菜单项的引用,保证子菜单定位准确。 - 测试验证:务必在真实移动设备或浏览器开发者工具的移动端视图中测试,确保触摸行为符合预期。
内容的提问来源于stack exchange,提问作者Alison Williams
相关产品推荐
相关产品推荐

