滚动或选择项目时,多选框弹出框不断跳跃

San*_*mar 13 reactjs material-ui

滚动或选择项目时,多选框弹出框不断跳跃

Codepen https://codesandbox.io/s/material-demo-e5j8h

import React from "react";
import { makeStyles } from "@material-ui/core/styles";
import Input from "@material-ui/core/Input";
import InputLabel from "@material-ui/core/InputLabel";
import MenuItem from "@material-ui/core/MenuItem";
import FormControl from "@material-ui/core/FormControl";
import ListItemText from "@material-ui/core/ListItemText";
import Select from "@material-ui/core/Select";
import Checkbox from "@material-ui/core/Checkbox";

const useStyles = makeStyles(theme => ({
  formControl: {
    margin: theme.spacing(1),
    minWidth: 120,
    maxWidth: 300
  },
  chips: {
    display: "flex",
    flexWrap: "wrap"
  },
  chip: {
    margin: 2
  },
  noLabel: {
    marginTop: theme.spacing(3)
  }
}));

const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
  PaperProps: {
    style: {
      maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
      width: 250
    }
  }
};

const names = [
  "Oliver Hansen",
  "Van Henry",
  "April Tucker",
  "Ralph Hubbard",
  "Omar Alexander",
  "Carlos Abbott",
  "Miriam Wagner",
  "Bradley Wilkerson",
  "Virginia Andrews",
  "Kelly Snyder"
];

export default function MultipleSelect() {
  const classes = useStyles();
  const [personName, setPersonName] = React.useState([]);

  const handleChange = event => {
    setPersonName(event.target.value);
  };

  return (
    <div>
      long text <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      <FormControl className={classes.formControl}>
        <InputLabel id="demo-mutiple-checkbox-label">Tag</InputLabel>
        <Select
          labelId="demo-mutiple-checkbox-label"
          id="demo-mutiple-checkbox"
          multiple
          value={personName}
          onChange={handleChange}
          input={<Input />}
          renderValue={selected => selected.join(", ")}
          MenuProps={MenuProps}
        >
          {names.map(name => (
            <MenuItem key={name} value={name}>
              <Checkbox checked={personName.indexOf(name) > -1} />
              <ListItemText primary={name} />
            </MenuItem>
          ))}
        </Select>
      </FormControl>
    </div>
  );
}
Run Code Online (Sandbox Code Playgroud)

Rya*_*ell 31

跳转的原因与.net的“内容锚点”功能有关Menu

来自https://material-ui.com/components/menus/#selected-menus(重点是我的):

如果用于项目选择,打开时,简单菜单会尝试将当前选中的菜单项与锚元素垂直对齐,初始焦点将放在选中的菜单项上。当前选择的菜单项是使用selected属性设置的(来自 ListItem)。要使用选定的菜单项而不影响菜单的初始焦点或垂直定位请将variant属性设置为menu

您还可以在变体 prop的文档中找到类似的注释。

文档的另一个相关部分是PopovergetContentAnchorEl 道具的描述

调用此函数是为了检索内容锚元素。它与anchorEl道具相反。内容锚元素应该是弹出框内的一个元素。它用于正确滚动和设置弹出框的位置。定位策略试图使内容锚元素正好位于锚元素上方。

Select元素的默认行为是将Select输入元素(关闭时显示所选项目的部分)用作“锚元素”,将最后选择的菜单项用作“内容锚元素”。这意味着当 处于Popover打开状态时,它会尝试将最后选择的菜单项(在 内Popover)与Select输入元素(在 之后Popover)对齐。

在使用 上的multiple属性的情况下Select,您有可能在Popover打开时更改最后选择的项目(对于单个选择,它通常会在选择某些内容后立即关闭)。此外,由于并非所有菜单项都能同时显示,最后选择的项目有时会滚动到视图之外,这迫使Popover垂直对齐使用稍微不同的逻辑。

所有这一切的最终效果是在您的沙箱中展示的奇怪跳跃。您可以通过指定以下内容强制Popover使用为零contentAnchorOffset来解决此问题getContentAnchorEl: null

const MenuProps = {
  PaperProps: {
    style: {
      maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
      width: 250
    }
  },
  getContentAnchorEl: null
};
Run Code Online (Sandbox Code Playgroud)

您可能还想添加variant: "menu"以摆脱一些自动聚焦行为,这将导致它自动滚动到最后一个选定的项目。这对于单选来说是很好的行为,但在多选中不太有用并且有些迷失方向。

设置variant: "menu"不足以(没有getContentAnchorEl: null)摆脱跳跃。这将导致它始终使用第一个菜单项作为内容锚点,这将导致更少的跳转,但由于第一个菜单项在更改选择时有时会滚动到视图之外,因此它仍然会进行一些跳转。

下面是沙箱的修改版本的完整代码,它不再有任何奇怪的跳跃(唯一的变化是MenuProps):

import React from "react";
import { makeStyles } from "@material-ui/core/styles";
import Input from "@material-ui/core/Input";
import InputLabel from "@material-ui/core/InputLabel";
import MenuItem from "@material-ui/core/MenuItem";
import FormControl from "@material-ui/core/FormControl";
import ListItemText from "@material-ui/core/ListItemText";
import Select from "@material-ui/core/Select";
import Checkbox from "@material-ui/core/Checkbox";

const useStyles = makeStyles(theme => ({
  formControl: {
    margin: theme.spacing(1),
    minWidth: 120,
    maxWidth: 300
  },
  chips: {
    display: "flex",
    flexWrap: "wrap"
  },
  chip: {
    margin: 2
  },
  noLabel: {
    marginTop: theme.spacing(3)
  }
}));

const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
  PaperProps: {
    style: {
      maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
      width: 250
    }
  },
  variant: "menu",
  getContentAnchorEl: null
};

const names = [
  "Oliver Hansen",
  "Van Henry",
  "April Tucker",
  "Ralph Hubbard",
  "Omar Alexander",
  "Carlos Abbott",
  "Miriam Wagner",
  "Bradley Wilkerson",
  "Virginia Andrews",
  "Kelly Snyder"
];

export default function MultipleSelect() {
  const classes = useStyles();
  const [personName, setPersonName] = React.useState([]);

  const handleChange = event => {
    setPersonName(event.target.value);
  };

  return (
    <div>
      long text <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      long text
      <br />
      <FormControl className={classes.formControl}>
        <InputLabel id="demo-mutiple-checkbox-label">Tag</InputLabel>
        <Select
          labelId="demo-mutiple-checkbox-label"
          id="demo-mutiple-checkbox"
          multiple
          value={personName}
          onChange={handleChange}
          input={<Input />}
          renderValue={selected => selected.join(", ")}
          MenuProps={MenuProps}
        >
          {names.map(name => (
            <MenuItem key={name} value={name}>
              <Checkbox checked={personName.indexOf(name) > -1} />
              <ListItemText primary={name} />
            </MenuItem>
          ))}
        </Select>
      </FormControl>
    </div>
  );
}
Run Code Online (Sandbox Code Playgroud)

编辑修复跳跃选择

  • @Jarrod 对于单一选择,这是一个合理的默认值,并且可能在原始的材料设计规范中。只有在多选情况下(这种情况很少见),行为才会变得奇怪。Selects 可能会在 v5 中进行重大修改,因此这个问题可能会得到解决(我认为这种行为不符合当前的 Material Design 规范)。 (3认同)
  • 很好的答案,谢谢 Ryan,但这让我想知道...... **为什么?!** 为什么这是默认行为。无论菜单的上下文如何,这似乎都是一个奇怪的用户体验决定,至少对于默认行为来说是这样。 (2认同)

小智 5

首先,感谢 Ryan Cogswell,他很好地解释了为什么会发生这种情况以及如何解决它。我试图解决多重选择期间选择跳跃的问题,并且由于您的回答而能够实现修复。我想补充的一件事是,对于像我这样使用打字稿的其他开发人员,如果您直接实现上述解决方案,您将遇到

" Type '{ PaperProps: { style: { float: string; minWidth: number; display: string; flexWrap: string; flexDirection: string; }; };variant: string; getContentAnchorEl: null; }' 不可分配给 type '部分'。属性'variant'的类型不兼容。类型'string'不可分配给类型'"menu" | "selectedMenu" | undefined'。TS2322 "

如果您遇到这种类型兼容性问题,请像下面这样直接声明 MenuProps 将修复它。

<Select
      labelId="demo-mutiple-checkbox-label"
      id="demo-mutiple-checkbox"
      multiple
      value={personName}
      onChange={handleChange}
      input={<Input />}
      renderValue={selected => selected.join(", ")}
      MenuProps={{
          PaperProps: {
            style: {
            maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
            width: 250
            }
        },
          variant: "menu",
          getContentAnchorEl: null
    }}>
Run Code Online (Sandbox Code Playgroud)

这对我的项目有用,但请告诉我是否有更好的解决方案来解决这种类型兼容性问题。