前端学习记录
  • 前言及目录
  • 前端基础
    • 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 提供支持
在本页
  • 目标组件
  • Basic Dialog Hooks

这有帮助吗?

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

Floating UI 使用经验分享 - Dialog

上一页Floating UI 使用经验分享 - Popover下一页Three.js 学习

最后更新于1年前

这有帮助吗?

上文:

在本文中,我将分享如何使用 Floating UI 来创建另一种常见的浮动 UI 组件——Dialog(对话框)。Dialog 是一个浮动元素,显示需要立即关注的信息,他会出现在页面内容上并阻止与页面的交互,直到它被关闭。

它与弹出框有类似的交互,但有两个主要区别:

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

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

一个可访问的对话框组件具有以下要点:

  • Dismissal:当用户按下 esc 键或在打开的对话框外按下时,它会关闭。

  • Role:元素被赋予相关的角色和 ARIA 属性,以便屏幕阅读器可以访问。

  • Focus management: 焦点完全被困在对话框中,必须由用户解除。

目标组件

目标:实现一个这样的 Dialog Demo 👇

Pasted image 20230616145507

接下来我们需要创建一个名为 Dialog 的 React 组件,它使用了 @floating-ui/react 库来创建一个可交互的浮动对话框。以下是对该组件的设想:

组件参数

Dialog 组件需要接受以下参数:

  • rootId:浮动元素的根元素,可选。

  • open:控制对话框是否打开的布尔值。

  • initialOpen:对话框初始是否打开的布尔值,默认为 false。

  • onOpenChange:当对话框打开状态改变时的回调函数,接受一个布尔值参数。

  • render:一个函数,接受一个对象参数,该对象包含一个 close 方法,用于关闭对话框。该函数返回要在对话框中渲染的 React 节点。

  • className:应用于对话框的 CSS 类名。

  • overlayClass:应用于浮动覆盖层的 CSS 类名。

  • containerClass:应用于对话框容器的 CSS 类名。

  • isDismiss:一个布尔值,决定是否启用点击外部区域关闭对话框的功能,默认为 true。

  • children:React 子元素,可以是一个按钮,点击后打开该弹窗。

  • showCloseButton:一个布尔值,决定是否显示关闭按钮,默认为 true。

组件功能

Dialog 组件的主要功能是创建一个可交互的浮动对话框,它可以通过点击关闭按钮或点击对话框外部区域来关闭。对话框的打开和关闭状态可以通过 open 和 onOpenChange 参数进行控制(受控),也可以通过内部状态进行自动管理(非受控)。

Dialog 组件使用了 @floating-ui/react 库的多个 Hook:

  • useFloating:用于管理对话框的打开和关闭状态。

  • useClick、useRole 和 useDismiss:用于处理对话框的交互,如点击和角色管理。

  • useInteractions:用于获取和设置交互属性。

此外,Dialog 组件还使用了 FloatingPortal、FloatingOverlay 和 FloatingFocusManager 组件来创建浮动对话框的 UI。

完整代码

结合实际可以写出这样一个功能较为完整的 Dialog 案例,可以自定义遮罩层、内部元素的样式,也可以控制点击遮罩层是否关闭弹窗等,还可以结合 Framer-motion 制作弹窗动画等(以后有机会也写一篇)

import {  
  FloatingFocusManager,  
  FloatingOverlay,  
  FloatingPortal,  
  useClick,  
  useDismiss,  
  useFloating,  
  useInteractions,  
  useRole,  
} from '@floating-ui/react';  
import clsx from 'clsx';  
import React, { cloneElement, useState } from 'react';  
import { CgClose } from 'react-icons![](file:///C:\Users\34504\AppData\Roaming\Tencent\QQTempSys\3)6GH))S[9A2G57O0%MM45V.gif)';  
  
type DialogProps = {  
  rootId?: string;  
  open?: boolean;  
  initialOpen?: boolean;  
  onOpenChange?: (open: boolean) => void;  
  children?: JSX.Element;  
  render: (props: { close: () => void }) => React.ReactNode;  
  className?: string;  
  overlayClass?: string;  
  containerClass?: string;  
  isDismiss?: boolean;  
  showCloseButton?: boolean;  
};  
  
export default function Dialog({  
  initialOpen = false,  
  open: controlledOpen,  
  onOpenChange: setControlledOpen,  
  children,  
  className,  
  render,  
  rootId: customRootId,  
  overlayClass,  
  containerClass,  
  showCloseButton = true,  
  isDismiss = true,  
}: DialogProps) {  
  const [uncontrolledOpen, setUncontrolledOpen] = useState(initialOpen);  
  const open = controlledOpen ?? uncontrolledOpen;  
  const setOpen = setControlledOpen ?? setUncontrolledOpen;  
  
  const { reference, floating, context } = useFloating({  
    open,  
    onOpenChange: setOpen,  
  });  
  
  const click = useClick(context);  
  const role = useRole(context);  
  const dismiss = useDismiss(context, { enabled: isDismiss, outsidePressEvent: 'mousedown' });  
  
  const { getReferenceProps, getFloatingProps } = useInteractions([click, role, dismiss]);  
  
  const onClose = () => setOpen(false);  
  
  return (  
    <>  
      {children && cloneElement(children, getReferenceProps({ ref: reference, ...children.props }))}  
      <FloatingPortal id={customRootId}>  
        {open && (  
          <FloatingOverlay  
            className={clsx('absolute inset-0 z-10 flex h-full w-full items-center', overlayClass ?? 'bg-black/60')}  
            lockScroll  
          >  
            <div className={clsx('m-auto grid place-items-center', containerClass)}>  
              <FloatingFocusManager context={context}>  
                <div  
                  className={clsx('relative overflow-hidden rounded-md bg-white', className ?? 'mx-24')}  
                  ref={floating}  
                  {...getFloatingProps()}  
                >  
                  {showCloseButton && <CgClose className="absolute right-2 top-2 h-6 w-6 cursor-pointer" onClick={onClose} />}  
                  {render({ close: onClose })}  
                </div>  
              </FloatingFocusManager>  
            </div>  
          </FloatingOverlay>  
        )}  
      </FloatingPortal>  
    </>  
  );  
}

Basic Dialog Hooks

此示例演示如何创建用于单个实例的对话框以熟悉基础知识。

让我们看一下这个例子:

Open state

import {useState} from 'react';
 
function Dialog() {
  const [isOpen, setIsOpen] = useState(false);
}

isOpen 确定对话框当前是否在屏幕上打开。它用于条件渲染。

useFloating hook

useFloating() hook为我们的对话提供上下文。我们需要传递一些信息:

  • open :来自我们上面的 useState() 挂钩的打开状态。

  • onOpenChange: 对话框打开或关闭时调用的回调函数。我们将使用它来更新我们的 isOpen 状态。

import {useFloating} from '@floating-ui/react';
 
function Dialog() {
  const [isOpen, setIsOpen] = useState(false);
 
  const {refs, context} = useFloating({
    open: isOpen,
    onOpenChange: setIsOpen,
  });
}

Interaction hooks

  • useClick() 添加了在单击引用元素时打开或关闭对话框的功能。但是,对话框可能不会附加到引用元素,因此这是可选的。(一般对话框都是独立出来Portal的,也就是上下文是body)

  • useDismiss() 添加了当用户按下 esc 键或在对话框外按下时关闭对话框的功能。可以将其的 outsidePressEvent 选项设置为 'mousedown' 以便触摸事件变得懒惰并且不会穿过背景,因为默认行为是急切的。(不太好理解,大概是)

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

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

import {
  // ...
  useClick,
  useDismiss,
  useRole,
  useInteractions,
  useId,
} from '@floating-ui/react';
 
function Dialog() {
  const [isOpen, setIsOpen] = useState(false);
 
  const {refs, context} = useFloating({
    open: isOpen,
    onOpenChange: setIsOpen,
  });
 
  const click = useClick(context);
  const dismiss = useDismiss(context, {
    outsidePressEvent: 'mousedown',
  });
  const role = useRole(context);
 
  // Merge all the interactions into prop getters
  const {getReferenceProps, getFloatingProps} = useInteractions([
    click,
    dismiss,
    role,
  ]);
 
  // Set up label and description ids
  const labelId = useId();
  const descriptionId = useId();
}

Rendering

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

function Dialog() {
  // ...
  return (
    <>
      <button ref={refs.setReference} {...getReferenceProps()}>
        Reference element
      </button>
      {isOpen && (
        <FloatingOverlay
          lockScroll
          style={{background: 'rgba(0, 0, 0, 0.8)'}}
        >
          <FloatingFocusManager context={context}>
            <div
              ref={refs.setFloating}
              aria-labelledby={labelId}
              aria-describedby={descriptionId}
              {...getFloatingProps()}
            >
              <h2 id={labelId}>Heading element</h2>
              <p id={descriptionId}>Description element</p>
              <button onClick={() => setIsOpen(false)}>
                Close
              </button>
            </div>
          </FloatingFocusManager>
        </FloatingOverlay>
      )}
    </>
  );
}
  • {...getReferenceProps()} / {...getFloatingProps()} 上一篇说过,将 props 从交互挂钩传播到相关元素上。它们包含诸如 onClick 、 aria-expanded 等道具。

FloatingPortal & FloatingOverlay & FloatingFocusManager

    • 提供了一个固定的基本样式,使背景内容变暗并阻止浮动元素后面的指针事件。

    • 它是一个常规的 <div/>,因此可以通过任何CSS解决方案进行样式设置。

  • <FloatingFocusManager />

    • 自动检测焦点变化,调整页面上的浮动元素的位置和状态,确保页面上所有元素的可访问性和可用性。

    • 默认情况通常将焦点捕获在内部。

      • <FloatingPortal />将浮动元素传送到给定的容器元素中——默认情况下,在应用程序根之外并进入 body。

      • 可以自定义 root,也就是可选择具有 id 的节点,或者创建它并将其附加到指定的根(body)

import {FloatingPortal} from '@floating-ui/react';
 
function Tooltip() {
  return (
    isOpen && (
      <FloatingPortal>
        <div>Floating element</div>
      </FloatingPortal>
    )
  );
}

官方示例 👉

<FloatingOverlay /> 是一个在浮动元素后面渲染背景覆盖元素的组件,具有锁定主体滚动的能力。

一个管理和控制页面中浮动元素焦点的组件

它应该直接包裹浮动元素,并且只在对话框也被渲染时才被渲染。

CodeSandbox demo
useFloating hook
Interaction hooks
Rendering
FloatingOverlay docs
FloatingPortal docs
FloatingFocusManager docs
Floating UI 使用经验分享 - Popover