前端学习记录
  • 前言及目录
  • 前端基础
    • HTML
    • CSS
      • CSS学习之布局
    • JavaScript
      • 跟着月影学JavaScript
      • JavaScript之对象、原型链及继承
      • JavaScript中的类
      • onclick与addEventListener区别
      • JS手撕题
    • HTTP与浏览器
      • HTTP实用指南
      • Web开发的安全之旅
    • 通用知识
      • 前端必须知道的开发调试知识
      • 前端设计模式应用
      • Web 标准与前端开发
  • 数据结构及算法
    • 数据结构
      • 1、线性表(List)
      • 2、堆栈(Stack)
      • 3、队列(Queue)
      • 4、二叉树(Binary Tree)
      • 5、二叉搜索树与平衡二叉树(BST & AVL)
      • 6、堆(Stack)& 哈夫曼树 & 并查集
      • 7、图(Graph)
        • 图论——解决最小生成树问题(Kruskal算法&Prim算法)
      • 8、排序(sort)
      • 9、散列表(hash)
      • 数据结构习题
        • 第一周:最大子列和算法、二分查找
        • 第二周:线性结构
        • 第三周:栽树(二叉树等)
        • 第四周:二叉搜索树&二叉平衡树
        • 第五周:堆&哈夫曼树&并查集
        • 第六周:图(上)连通集 、DFS&BFS
        • 第七周:图(中)Floyd算法求最短路
        • 第八周:图(下)
        • 第九周:排序(上)归并&堆排序
        • 第十周:排序(下)
        • 第十一周:散列查找 & KMP
    • CS基础
      • 编译原理 实验一 词法分析器设计
      • 编译原理 实验二 LL(1)分析法程序
    • LeetCode
      • 冲刺春招-精选笔面试 66 题大通关
        • day1:21. 合并两个有序链表、146. LRU 缓存、25. K 个一组翻转链表
        • day2:14. 最长公共前缀、3. 无重复字符的最长子串、124. 二叉树中的最大路径和
        • day3:206. 反转链表、199. 二叉树的右视图、bytedance-016最短移动距离
        • day4:1. 两数之和、15. 三数之和、42. 接雨水
        • day5:7. 整数反转、215. 数组中的第K个最大元素、23. 合并K个升序链表
        • day6:33. 搜索旋转排序数组、54. 螺旋矩阵、bytedance-006. 夏季特惠
        • day7:53. 最大子数组和、152. 乘积最大子数组、41. 缺失的第一个正数
        • day8:20. 有效的括号、200. 岛屿数量、76. 最小覆盖子串
        • day9:105. 从前序与中序遍历序列构造二叉树、103. 二叉树的锯齿形层序遍历、bytedance-010. 数组组成最大数
        • day10:94. 二叉树的中序遍历、102. 二叉树的层序遍历、394. 字符串解码
        • day11:415. 字符串相加、5. 最长回文子串、72. 编辑距离
        • day12:64. 最小路径和、300. 最长递增子序列、bytedance-004. 机器人跳跃问题
        • day13:88. 合并两个有序数组、31. 下一个排列、4. 寻找两个正序数组的中位数
        • day14:121. 买卖股票的最佳时机、56. 合并区间、135. 分发糖果
        • day15:232. 用栈实现队列、22. 括号生成、128. 最长连续序列
        • day16:bytedance-007. 化学公式解析、129. 求根节点到叶节点数字之和、239. 滑动窗口最大值
        • day17:141. 环形链表、236. 二叉树的最近公共祖先、92. 反转链表 II
        • day18:322. 零钱兑换、198. 打家劫舍、 bytedance-003. 古生物血缘远近判定
        • day19:160. 相交链表、143. 重排链表、142. 环形链表 II
        • day20:704. 二分查找、43. 字符串相乘、bytedance-002. 发下午茶
        • day21题目:69. x 的平方根、912. 排序数组、887. 鸡蛋掉落
        • day22:151. 颠倒字符串中的单词、46. 全排列、2. 两数相加
      • 剑指 Offer
        • 剑指offer day1 栈与队列(简单)
        • 剑指offer day2 链表(简单)
        • 剑指offer day3 字符串(简单)
        • 剑指offer day4 查找算法(简单)
        • 剑指offer day5 查找算法(中等)
        • 剑指offer day6 搜索与回溯算法(简单)
        • 剑指offer day7 搜索与回溯算法(简单)
        • 剑指offer day8 动态规划(简单)
        • 剑指offer day9 动态规划(中等)
        • 剑指offer day10 动态规划(中等)
        • 剑指offer day11 双指针(简单)
        • 剑指offer day12 双指针(简单)
        • 剑指offer day13 双指针(简单)
        • 剑指offer day14 搜索与回溯算法(中等)
        • 剑指offer day15 搜索与回溯算法(中等)
        • 剑指offer day16 排序(简单)
        • 剑指offer day17 排序(中等)
      • 剑指 Offer 专项突击版
  • 前端进阶
    • React
      • 响应式系统与 React
      • React学习小记
      • Redux学习之Redux三原则、createSore原理及实现
    • Vue
    • TypeScript
      • TypeScript入门
      • TypeScript 类型体操练习
        • Easy题(13/13)
        • Middle(20/72)
    • 前端工程化
      • Webpack知识体系
    • Node
    • 前端动画与绘图
      • WebGL基础
      • 前端动画简介
      • Floating UI 使用经验分享 - Popover
      • Floating UI 使用经验分享 - Dialog
      • Three.js 学习
        • 学习记录
        • 资源记录
    • 前端性能优化
    • 跨端
      • RN 学习小记之使用 Expo 创建项目
    • 开源
    • SEO 优化
      • 搜索引擎优化 (SEO) 新手指南笔记
  • 笔面试记录
    • 面经集锦
      • 2022春暑期实习MetaApp一二面面经
      • 2022春暑期实习字节前端一面凉经
    • 笔试复盘
      • 2022春暑期实习-美团前端-笔试
      • 2022春暑期实习-360前端-笔试(AK)
      • 2022春暑期实习-京东前端-笔试
      • 2022春暑期实习-网易雷火前端-笔试(AK)
      • 2022春暑期实习-网易互联网前端-暑期实习笔试
由 GitBook 提供支持
在本页
  • 前言及介绍
  • 安装
  • Popover
  • 结语

这有帮助吗?

在GitHub上编辑
导出为 PDF
  1. 前端进阶
  2. 前端动画与绘图

Floating UI 使用经验分享 - Popover

上一页前端动画简介下一页Floating UI 使用经验分享 - Dialog

最后更新于1年前

这有帮助吗?

前言及介绍

在当今的前端开发中,浮动元素扮演着越来越重要的角色。它们能够为用户提供额外的交互和信息,同时不会影响页面的整体布局。而 Floating UI 就是一个为了方便定位和创建浮动元素的 JavaScript 库。通过它,你可以轻松地 控制浮动元素的位置和交互效果,从而提升用户体验。

如果你正在寻找一个简单易用的浮动元素解决方案,或许 Floating UI 不是你的最佳选择,该库的主要目标是提供锚点定位的功能,而不是提供预建样式或其他高级交互效果。但如果你是熟练掌握 React 并希望使用这样高度自定义的库,你就可以更好地使用它。

这个库是有意 “低级” 的,它的唯一目标是提供 “锚点定位”。把它想象成一个缺失的 CSS 特性的 polyfill。不提供预建样式,用户交互仅适用于 React 用户。 如果您正在寻找开箱即用的简单功能,您可能会发现其他库更适合您的用例。

写这篇文章我更愿意称之为在 React 中使用 Floating UI 的经验分享,特别是在 React 中使用该库的方法,而不是教程,因为实际运用的时候发现 Floating UI 的文档和示例已经相当详尽,但苦于 Floating UI 的中文资料寥寥无几,所以自己沉淀一些方便日后回忆,也希望能为需要的人提供一些帮助和参考。(当然,最快的还是直接去看英文文档和例子,搜索 API 的使用)

安装

你可以通过包管理器或 CDN 来安装 Floating UI。如果你使用 npm、yarn 或 pnpm,你可以运行以下命令:

npm install @floating-ui/dom

如果你使用 CDN,你可以在你的 HTML 文件中添加以下标签:

<script src="https://cdn.jsdelivr.net/npm/@floating-ui/dom"></script>

更多请看:

React 中安装

在 React 中安装只需要安装@floating-ui/react 这个包即可

yarn add @floating-ui/react

Popover

在本文中,我将分享如何使用 Floating UI 来创建一种常见的浮动 UI 组件——Popover(弹出框)。Popover 是一种常见的浮动 UI 组件,它通常在用户悬停或点击某个元素时显示,以提供额外的信息或选项。

案例演示

useFloating

首先就是核心 hook —— useFloating

useFloating hook 为浮动元素提供定位和上下文。我们需要传递一些信息:

  • open :弹窗的打开状态。

  • onOpenChange : 弹窗打开或关闭时将调用的回调函数。floating-ui 内部将使用它来更新它们的 isOpen 状态。

  • placement :浮动元素相对参考元素的位置,默认位置是 'bottom' ,但您可能希望将工具提示放置在与按钮相关的任何位置。为此,Floating UI 具有 placement 选项。

    • 可用的基本位置是 'top' 、 'right' 、 'bottom' 、 'left' 。

    • 这些基本位置中的每一个都以 -start 和 -end 的形式对齐。例如, 'right-start' 或 'bottom-end' 。这些允许您将工具提示与按钮的边缘对齐,而不是将其居中。

  • middleware :将中间件导入并传递到数组,以确保弹窗保留在屏幕上,无论它最终被放置在哪里。

  • whileElementsMounted :只有在参考元素和浮动元素都挂载好的情况下,才会在必要时更新位置,以确保浮动元素保持锚定在参考元素上。

import { useFloating, autoUpdate, offset, flip, shift } from '@floating-ui/react';

function Popover() {
  const [isOpen, setIsOpen] = useState(false);

  const { x, y, strategy, refs, context } = useFloating({
    open: isOpen,
    onOpenChange: setIsOpen,
    middleware: [offset(10), flip(), shift()],
    placement: 'top',
    whileElementsMounted: autoUpdate,
  });
}

Interaction hooks - useInteractions

使用 useInteractions 传入一个配置对象,可以使浮动元素能够拓展打开、关闭行为或被屏幕阅读器访问等额外功能。在这个例子中,useClick() 添加了在单击引用元素时切换弹出窗口打开或关闭的功能。useDismiss() 添加了当用户按下 esc 键或在弹出框外按下时关闭弹出框的功能。useRole() 将 dialog 的正确 ARIA 属性添加到弹出框和引用元素。最后,useInteractions() 将他们所有的 props 合并到 prop getters 中,可以用于渲染。[^1]

一些配置的对象。使浮动元素能够拓展打开、关闭行为或被屏幕阅读器访问等额外功能。

import {
  // ...
  useClick,
  useDismiss,
  useRole,
  useInteractions,
} from '@floating-ui/react';

function Popover() {
  const [isOpen, setIsOpen] = useState(false);

  const { x, y, reference, floating, strategy, context } = useFloating({
    open: isOpen,
    onOpenChange: setIsOpen,
    middleware: [offset(10), flip(), shift()],
    whileElementsMounted: autoUpdate,
  });

  const click = useClick(context);
  const dismiss = useDismiss(context);
  const role = useRole(context);

  // Merge all the interactions into prop getters
  const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss, role]);
}
  • useClick() 添加了在单击引用元素时切换弹出窗口打开或关闭的功能。

  • useDismiss() 添加了当用户按下 esc 键或在弹出框外按下时关闭弹出框的功能。

  • useRole() 将 dialog 的正确 ARIA 属性添加到弹出框和引用元素。

最后,useInteractions() 将他们所有的 props 合并到 prop getters 中,可以用于渲染。将他们所有的 props 合并到可用于渲染的 prop getters 。

Rendering

现在我们已经设置了所有的变量和 hook,我们可以渲染我们的元素了。

function Popover() {
  // ...
  return (
    <>
      <button ref={refs.setReference} {...getReferenceProps()}>
        Reference element
      </button>
      {isOpen && (
        <FloatingFocusManager context={context} modal={false}>
          <div
            ref={refs.setFloating}
            style={{
              position: strategy,
              top: y ?? 0,
              left: x ?? 0,
              width: 'max-content',
            }}
            {...getFloatingProps()}
          >
            Popover element
          </div>
        </FloatingFocusManager>
      )}
    </>
  );
}
  • getReferenceProps & getFloatingProps 由 useInteractions 返回传播到相关元素上。它们包含诸如 onClick 、 aria-expanded 等相关的 props。

Modal and non-modal behavior 模态或非模态行为

在上面的例子中我们使用了非模态的焦点管理,但是弹出框的焦点管理行为可以是模态的也可以是非模态的。它们的区别如下:

Modal 模态

  • 弹出窗口及其内容是 唯一可以接收焦点 的元素。当弹出窗口打开时,用户无法与页面的其余部分(屏幕阅读器也不能)交互,直到弹出窗口关闭。

  • 需要一个 明确的关闭按钮(尽管它可以在视觉上隐藏)。

此行为是默认行为:

<FloatingFocusManager context={context}>
  <div />
</FloatingFocusManager>

Non-modal 非模态行为

  • 弹出窗口及其内容可以获得焦点,但用户仍然可以与页面的其余部分进行交互。

  • 当在其外部进行 Tab 键时,弹出窗口会在失去焦点时自动关闭,并且自然 DOM 顺序中的下一个可聚焦元素获得焦点。

  • 不需要明确的关闭按钮。

此行为可以使用 modal prop 进行配置,如下所示:

<FloatingFocusManager context={context} modal={false}>
  <div />
</FloatingFocusManager>

完整代码

经过亿点点优化,就能简简单单造出这么一个 Popover 组件啦~

import {
  FloatingFocusManager,
  Placement,
  autoUpdate,
  useFloating,
  useInteractions,
  shift,
  offset,
  flip,
  useClick,
  useRole,
  useDismiss,
} from '@floating-ui/react';
import { cloneElement, useEffect, useId, useState } from 'react';

type PopoverProps = {
  disabled?: boolean;
  open?: boolean;
  onOpenChange?: (open: boolean) => void;
  render: (props: { close: () => void }) => React.ReactNode;
  placement?: Placement;
  children: JSX.Element;

  className?: string;
};
const Popover = ({ disabled, children, render, placement, open: passedOpen, onOpenChange }: PopoverProps) => {
  const [open, setOpen] = useState(false);
  const { x, y, reference, floating, strategy, context } = useFloating({
    open,
    onOpenChange: (op) => {
      if (disabled) return;
      setOpen(op);
      onOpenChange?.(op);
    },
    middleware: [offset(10), flip(), shift()],
    placement,
    whileElementsMounted: autoUpdate,
  });
  const { getReferenceProps, getFloatingProps } = useInteractions([useClick(context), useRole(context), useDismiss(context)]);

  const headingId = useId();

  useEffect(() => {
    if (passedOpen === undefined) return;
    setOpen(passedOpen);
  }, [passedOpen]);

  return (
    <>
      {cloneElement(children, getReferenceProps({ ref: reference, ...children.props }))}
      {open && (
        <FloatingFocusManager context={context} modal={false}>
          <div
            ref={floating}
            style={{
              position: strategy,
              top: y ?? 0,
              left: x ?? 0,
            }}
            className="z-10 bg-yellow-400 p-2 outline-none"
            aria-labelledby={headingId}
            {...getFloatingProps()}
          >
            {render({
              close: () => {
                setOpen(false);
                onOpenChange?.(false);
              },
            })}
          </div>
        </FloatingFocusManager>
      )}
    </>
  );
};
export default Popover;

结语

  • 它是模态的,并在对话框后面呈现一个背景,使后面的内容变暗,使页面的其余部分无法访问。

  • 它在视口中居中,不锚定到任何特定的参考元素。

通过以下学习,可以轻易构建一个点击弹出的气泡框/弹出层,如图。 Demo 演示 👉

image.png

当您不知道哪个位置最适合浮动元素,或者不想明确指定它时,这个中间件很有用。

其他中间件,可以看文档,包括 (设置偏移) 、 (添加小箭头) 、(沿着指定的轴移动浮动元素以使其保持可见)、(翻转浮动元素的位置以使其保持可见)、 (改进跨多行的内联引用元素的定位) 等有用的中间件

如果用户滚动或调整屏幕大小,浮动元素可能会与参考元素分离,因此需要再次更新其位置以确保其保持锚定状态。

<FloatingFocusManager /> 是 管理模态或非模态行为( modal and non-modal ) 的 弹出框焦点 的组件。它应该直接包裹浮动元素,并且 只在 popover 也被渲染时才被渲染。 FloatingFocusManager —— .

模态或非模态行为

下一次将介绍 的创建及封装,包括 FloatingPortal 和 FloatingOverlay 的介绍,它与弹出框有类似的交互,但有两个主要区别:

非常推荐大家去阅读 ,它的思想我非常喜欢,无论是中间件还是 hook 的抽象程度

Getting Started | Floating UI
CodeSandbox
autoPlacement
Middleware | Floating UI
offset
arrow
shift
flip
inline
autoUpdate
Interaction hooks
Rendering
FloatingFocusManager docs
Modal and non-modal behavior
Modal
Non-modal
Dialog | Floating UI
Floating UI 官方文档