首页 > 解决方案 > 为什么当底层组件重新渲染时popper会跳到左上角?

问题描述

我正在使用 Material-UI Popper 组件(它又使用 popper.js)来创建一个悬停工具栏。在大多数情况下,它运行良好,除了一种奇怪的行为:

  1. 选择一些文本:悬停的工具栏出现在文本上方 - 正如预期的那样。
  2. 选择工具栏中的任何按钮:执行相应的操作。但是,工具栏会跳到窗口的左上角。见下文。

在此处输入图像描述

您可以在我的 Storybook中尝试这种行为- 只需选择一些文本并单击其中一个“T”按钮。

基本问题围绕popper的定位:

  1. 当用户选择一些文本时,会创建一个虚假的虚拟元素并将其作为锚元素传递给 popper。Popper 使用它anchorEl来定位悬停工具栏。到现在为止还挺好。
  2. 当用户单击工具栏中的按钮时,悬停的工具栏会跳到窗口的左上角。

我猜这是因为当底层组件重新渲染时,锚元素会以某种方式丢失。我不知道为什么,但这只是我的理论。有人可以帮我解决这个问题吗?

计算 的代码anchorEl位于 ReactuseEffect()中。我已确保 的依赖项列表useEffect是准确的。我可以看到,当工具栏跳转时,useEffect()没有被调用,这意味着anchorEl没有被重新计算。这让我相信工具栏应该在当前位置保持原样,而不是跳转到 (0,0)。但这并没有发生:-(。

这是useEffect()工具栏组件内的代码。你可以在我的 repo中找到完整的代码。任何帮助将非常感激。

useEffect(() => {
    if (editMode === 'toolbar') {
        if (isTextSelected) {
            const domSelection = window.getSelection();
            if (domSelection === null || domSelection.rangeCount === 0) {
                return;
            }
            const domRange = domSelection.getRangeAt(0);
            const rect = domRange.getBoundingClientRect();
            setAnchorEl({
                clientWidth: rect.width,
                clientHeight: rect.height,
                getBoundingClientRect: () =>
                    domRange.getBoundingClientRect(),
            });
            setToolbarOpen(true);
        } else {
            setToolbarOpen(false);
        }
    } else {
        setToolbarOpen(false);
    }
}, [editMode, isTextSelected, selection, selectionStr]);

标签: reactjsmaterial-uiuse-effectpopper.jsslate.js

解决方案


我相信您在toggleBlockdomRange完成工作后不再有效(由于 dom 节点被替换),因此不再返回任何有意义的东西。getBoundingClientRect

您应该能够通过重做获取 anchorEl 范围内的工作来解决此问题getBoundingClientRect。可能类似于以下内容(我没有尝试执行它,所以不能保证没有小错误):

const getSelectionRange = () => {
  const domSelection = window.getSelection();
  if (domSelection === null || domSelection.rangeCount === 0) {
    return null;
  }
  return domSelection.getRangeAt(0);
};
useEffect(() => {
  if (editMode === "toolbar") {
    if (isTextSelected) {
      const domRange = getSelectionRange();
      if (domRange === null) {
        return;
      }
      const rect = domRange.getBoundingClientRect();
      setAnchorEl({
        clientWidth: rect.width,
        clientHeight: rect.height,
        getBoundingClientRect: () => {
          const innerDomRange = getSelectionRange();
          return innerDomRange === null
            ? null
            : innerDomRange.getBoundingClientRect();
        }
      });
      setToolbarOpen(true);
    } else {
      setToolbarOpen(false);
    }
  } else {
    setToolbarOpen(false);
  }
}, [editMode, isTextSelected, selection, selectionStr]);

推荐阅读