You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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:

  1. 追踪交互是否由触摸触发(避免移动端误触跳转)
  2. 为桌面端单独处理悬停展开逻辑
  3. 用状态控制子菜单的显示/隐藏
  4. 在触摸触发的"点击"事件中阻止页面跳转

具体实现代码示例

这里是一个基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 19:02:45