0Pricing
React Academy · 课时

使用 Portals 构建工具提示与下拉菜单

使用 Portals 渲染工具提示和下拉菜单,使其脱离 overflow-hidden 容器的限制。

使用 Portals 构建工具提示与下拉菜单 是 CoddyKit 上的免费 React Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 React Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 React Academy 课程共包含 4 节课。

欢迎

在本课中,您将构建工具提示和下拉菜单组件。它们使用 React 门户将内容渲染到 document.body 中,从而避开父元素的溢出隐藏和 z-index 堆叠上下文限制。

溢出问题

如果任意祖先元素具有 `overflow: hidden` 或 `overflow: scroll`,相对于触发按钮定位的工具提示就会被裁剪。通过门户将工具提示移到 document.body 中可以彻底解决这个问题。

定位策略

要正确定位通过门户渲染的工具提示,请使用 `getBoundingClientRect()` 测量触发元素的边界矩形,并计算考虑滚动位置的绝对坐标。
const rect = triggerRef.current.getBoundingClientRect();
const top = rect.bottom + window.scrollY + 8;
const left = rect.left + window.scrollX;

工具提示组件

构建一个 Tooltip 来包裹其子元素,为触发元素附加引用,并在悬停时通过门户渲染定位后的工具提示框。
function Tooltip({ content, children }) {
  const [visible, setVisible] = useState(false);
  const [pos, setPos] = useState({ top: 0, left: 0 });
  const ref = useRef();

  const show = () => {
    const r = ref.current.getBoundingClientRect();
    setPos({ top: r.bottom + window.scrollY + 4,
             left: r.left + window.scrollX });
    setVisible(true);
  };

  return (
    <span ref={ref} onMouseEnter={show} onMouseLeave={() => setVisible(false)}>
      {children}
      {visible && createPortal(
        <div style={{ position:'absolute', top: pos.top, left: pos.left }}
             className="tooltip">{content}</div>,
        document.body
      )}
    </span>
  );
}

滚动与调整大小时更新位置

如果用户在工具提示可见时滚动页面,其位置就会过时。请监听 `scroll` 和 `resize` 事件来重新计算位置,或关闭工具提示。
useEffect(() => {
  if (!visible) return;
  const update = () => setVisible(false);
  window.addEventListener('scroll', update, true);
  window.addEventListener('resize', update);
  return () => {
    window.removeEventListener('scroll', update, true);
    window.removeEventListener('resize', update);
  };
}, [visible]);

下拉菜单架构

下拉菜单遵循与工具提示相同的模式。触发按钮显示或隐藏菜单,菜单通过门户渲染,而点击外部处理程序会将其关闭。

点击外部处理程序

在 document 上使用 `mousedown` 监听器来检测菜单外部的点击。将点击目标分别与菜单引用和触发元素引用进行比较,以决定是否关闭。
useEffect(() => {
  if (!open) return;
  const handler = (e) => {
    if (!menuRef.current?.contains(e.target) &&
        !triggerRef.current?.contains(e.target)) {
      setOpen(false);
    }
  };
  document.addEventListener('mousedown', handler);
  return () => document.removeEventListener('mousedown', handler);
}, [open]);

下拉菜单中的键盘导航

实现 ArrowDown/ArrowUp 在菜单项之间移动,使用 Enter 进行选择,并使用 Escape 关闭菜单。在状态中跟踪获得 focus 的索引,然后以编程方式对菜单项调用 `.focus()`。

使用定位库

在生产环境中,请使用 **@floating-ui/react**(以前称为 Popper.js)。它会自动处理翻转(靠近视口边缘时重新定位)、shift(保持工具提示可见)和箭头定位。
import { useFloating, flip, shift } from '@floating-ui/react';

const { refs, floatingStyles } = useFloating({
  middleware: [flip(), shift()],
});

可访问性:工具提示的 ARIA

对于工具提示,请在工具提示元素上添加 `role='tooltip'`,并在触发元素上添加指向工具提示 id 的 `aria-describedby`。这样屏幕阅读器就能播报工具提示内容。
<button aria-describedby="tip1">Hover me</button>
{visible && createPortal(
  <div id="tip1" role="tooltip">Helpful text</div>,
  document.body
)}

快速检查

如何计算通过门户渲染的工具提示的正确屏幕位置?

回顾

Portal 可以让工具提示和下拉菜单脱离设置了溢出隐藏的父元素。使用 getBoundingClientRect 测量触发元素,为绝对定位添加滚动偏移量,在点击外部或滚动时关闭,并添加 ARIA 属性以提升可访问性。

课程完成

恭喜您!您已完成 **React 中的传送门与模态框**。现在您可以构建具有良好可访问性、定位准确的模态框、工具提示和下拉菜单,使它们不受任何 CSS 层叠限制的影响。

常见问题解答

「使用 Portals 构建工具提示与下拉菜单」课时是免费的吗?

是的 — 「使用 Portals 构建工具提示与下拉菜单」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 React Academy 课程的其余内容,请升级到 CoddyKit PRO。 React Academy 课程共包含 4 节课。

「使用 Portals 构建工具提示与下拉菜单」这节课中我会学到什么?

使用 Portals 渲染工具提示和下拉菜单,使其脱离 overflow-hidden 容器的限制。 你通过在浏览器中直接运行的动手代码来练习 React Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 React Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 React Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「使用 Portals 构建工具提示与下拉菜单」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 React Academy 课中编写并运行代码吗?

能。每节 React Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 什么是 React Portals?
  2. 使用 Portals 构建模态框组件
  3. 处理模态框中的焦点与键盘陷阱
  4. 使用 Portals 构建工具提示与下拉菜单
← 返回 React Academy