在React中实现鼠标悬浮打开菜单和鼠标移出关闭菜单

8
我刚开始尝试着使用 React。目前我正在使用 material-ui 和 react 来制作我的导航栏。当我悬停在菜单上时,下拉菜单会出现。但是为了关闭下拉菜单,我必须点击下拉菜单外部。我希望当鼠标移开下拉菜单或者移动到不同的菜单选项时(此时应该显示不同的下拉菜单),可以关闭下拉菜单。就像这个网站一样:https://www.palantir.com/ 我找过但没找到解决方案。最接近的是这个链接:Material-ui: open menu by event hover 我尝试使用相同的技术将其添加到我的代码中,但没有效果。有什么建议吗?谢谢!
编辑:我在这里重现了我的问题:https://react-xmaiyw.stackblitz.io 当点击“Why us”时可以看到问题。
 handleClick = (event) => {
 event.preventDefault();

   this.setState({
    open: true,
    anchorEl: event.currentTarget,
   });
 };

handleRequestClose = () => {
  this.setState({
   open: false,
  });
};

render() {
return (
  <FlatButton
  onClick={this.handleClick}
  onMouseOver={this.handleClick}
  onMouseLeave={this.handleRequestClose} //When I add this line of 
     //code, it keeps flickering very fast almost as if drop-down 
     //doesn't open
  label="Why Us?"
/>
)}

1
我不确定这是否是你要找的答案,因为你似乎已经写了很多javascript,但是可以使用CSS伪元素hover来实现这个效果,而不需要使用javascript。W3Schools有一个关于如何在下拉菜单中实现这一点的优秀教程在这里。如果这不是你想要的效果,请包含你的标记,以便我们更全面地了解你的需求。 - user9150676
我正在尝试使用js使其与CSS相比更加有效。我已经编辑了我的原始问题并添加了一个解决方案链接。谢谢 :) - abidishajia
2个回答

17
闪烁是由于鼠标下方的菜单打开而引起的。当菜单打开时,鼠标不再位于按钮上方,因此会触发mouseleave事件,关闭菜单,这样您的鼠标现在又位于按钮上方,触发mouseenter事件,打开菜单......如此往复。
您可以通过一些额外的逻辑来跟踪鼠标的位置,并设置超时以确保用户有足够的时间在按钮和菜单之间进行过渡,从而实现您想要的效果。
import React from 'react';
import Button from 'material-ui/Button';
import Menu, { MenuItem } from 'material-ui/Menu';

const timeoutLength = 300;

class SimpleMenu extends React.Component {
  state = {
    anchorEl: null,

    // Keep track of whether the mouse is over the button or menu
    mouseOverButton: false,
    mouseOverMenu: false,
  };

  handleClick = event => {
    this.setState({ open: true, anchorEl: event.currentTarget });
  };

  handleClose = () => {
    this.setState({ mouseOverButton: false, mouseOverMenu: false });
  };

  enterButton = () => {
    this.setState({ mouseOverButton: true });
  }

  leaveButton = () => {
    // Set a timeout so that the menu doesn't close before the user has time to
    // move their mouse over it
    setTimeout(() => {
      this.setState({ mouseOverButton: false });
    }, timeoutLength);
  }

  enterMenu = () => {
    this.setState({ mouseOverMenu: true });
  }

  leaveMenu = () => {
     setTimeout(() => {
      this.setState({ mouseOverMenu: false });
     }, timeoutLength);
  }

  render() {
    // Calculate open state based on mouse location
    const open = this.state.mouseOverButton || this.state.mouseOverMenu;

    return (
      <div>
        <Button
          aria-owns={this.state.open ? 'simple-menu' : null}
          aria-haspopup="true"
          onClick={this.handleClick}
          onMouseEnter={this.enterButton}
          onMouseLeave={this.leaveButton}
        >
          Open Menu
        </Button>
        <Menu
          id="simple-menu"
          anchorEl={this.state.anchorEl}
          open={open}
          onClose={this.handleClose}
          MenuListProps={{
            onMouseEnter: this.enterMenu,
            onMouseLeave: this.leaveMenu,
          }}

        >
          <MenuItem onClick={this.handleClose}>Profile</MenuItem>
          <MenuItem onClick={this.handleClose}>My account</MenuItem>
          <MenuItem onClick={this.handleClose}>Logout</MenuItem>
        </Menu>
      </div>
    );
  }
}

export default SimpleMenu;

我使用了MenuListProps来直接在MenuList本身上设置mouseEntermouseLeave事件,因为Menu组件包含一堆不可见的(disply: none)过渡元素,这些元素对鼠标事件有奇怪的影响。实际显示的元素是MenuList,所以直接在它上面设置鼠标事件是有意义的。

您可能需要调整timeoutLength和过渡效果,使一切看起来更加流畅。


2
我遇到了相同的问题。 我是这样解决的。我将LeaveMenu事件分别赋予总组件和菜单组件,之后它就完美地工作了。
import React from 'react';
import {
  Menu,
  MenuItem as MuiMenuItem,
  Avatar,
  Divider,
  Typography,
  Switch,
  Fade,
} from '@mui/material';
import { useHistory } from 'react-router-dom';
import { styled } from '@mui/styles';
import { DarkMode as DarkModeIcon } from '@mui/icons-material';

/********************  Styled Components  ********************/
const UserAvatarButton = styled('div')(({ active, theme }) => ({
  height: 72,
  display: 'flex',
  justifyContent: 'center',
  alignItems: 'center',
  padding: '0px 20px',
  cursor: 'pointer',
  borderBottom: active ? `3px solid ${theme.palette.primary.main}` : 'none',
  borderRadius: 0,
}));

const ProfileMenuNavigation = styled(Menu)(() => ({
  '& .MuiList-root': {
    paddingTop: 0,
    paddingBottom: 0,
    minWidth: 220,
    maxWidth: 350,
  },
}));

const MenuItem = styled(MuiMenuItem)(({ theme }) => ({
  padding: 16,
  width: '100%',
  '&:hover': {
    backgroundColor: theme.palette.background.main,
    boxShadow: '5px 0px 5px 0px #888888',
    transition: 'box-shadow 0.3s ease-in-out',
  },
}));

const ProfileMenuText = styled(Typography)(() => ({
  fontFamily: 'Poppins',
  marginLeft: 16,
  marginRight: 16,
  fontSize: 16,
  fontWeight: 600,
}));

/********************  Main Component  ********************/
const ProfileMenu = ({ menus, active }) => {
  const history = useHistory();

  const [anchorEl, setAnchorEl] = React.useState(null);
  const open = Boolean(anchorEl);

  const handleClick = (event) => {
    if (anchorEl) {
      setAnchorEl(null);
    } else {
      setAnchorEl(event.currentTarget);
    }
  };

  const handleClose = () => {
    setAnchorEl(null);
  };

  const goPath = (path) => {
    setAnchorEl(null);
    history.push(path);
  };

  const leaveMenu = () => {
    setTimeout(() => {
      setAnchorEl(null);
    }, 300);
  };

  return (
    <div onMouseLeave={leaveMenu}>
      <UserAvatarButton
        id="account-button"
        active={active}
        aria-controls={open ? 'account-menu' : undefined}
        aria-haspopup="true"
        aria-expanded={open ? 'true' : undefined}
        onClick={handleClick}
        onMouseOver={(event) => setAnchorEl(event.currentTarget)}
      >
        <Avatar
          sx={{
            width: 38,
            height: 38,
          }}
          alt="Avatar"
          src="https://i.pravatar.cc/300"
        />
      </UserAvatarButton>
      <ProfileMenuNavigation
        id="account-menu"
        anchorEl={anchorEl}
        open={open}
        onClose={handleClose}
        MenuListProps={{
          'aria-labelledby': 'account-button',
          onMouseLeave: leaveMenu,
        }}
        anchorOrigin={{
          vertical: 'bottom',
          horizontal: 'right',
        }}
        transformOrigin={{
          vertical: 'top',
          horizontal: 'right',
        }}
        TransitionComponent={Fade}
      >
        {menus.map((menu, index) => (
          <div key={index}>
            <MenuItem onClick={() => goPath(menu.path)}>
              {menu?.icon}
              <ProfileMenuText>{menu.text}</ProfileMenuText>
            </MenuItem>
            <Divider style={{ margin: 0 }} />
          </div>
        ))}
        <MenuItem onClick={() => {}}>
          <DarkModeIcon />
          <ProfileMenuText>Night Mode</ProfileMenuText>
          <div style={{ marginLeft: 16 }}>
            <Switch />
          </div>
        </MenuItem>
      </ProfileMenuNavigation>
    </div>
  );
};

export default ProfileMenu;

2
目前你的回答不够清晰。请编辑并添加更多细节,以帮助其他人理解它如何回答所提出的问题。你可以在帮助中心找到有关如何撰写好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接