logoAnt Design

⌘ K
  • 设计
  • 研发
  • 组件
  • 博客
  • 资源
  • 国内镜像
5.25.4
  • v6 的一些 CSS 琐事
  • 👀 视觉回归测试
  • 为什么禁用日期这么难?
  • 封装 Form.Item 实现数组转对象
  • 行省略计算
  • 📢 v4 维护周期截止
  • antd 里常用的 TypeScript 工具方法
  • 一个构建的幽灵
  • 当 Ant Design 遇上 CSS 变量
  • API 的历史债务
  • 灵动的 Notification
  • 色彩模型与颜色选择器
  • 主题拓展
  • 虚拟表格来了!
  • 快乐工作主题(一)
  • 动态样式去哪儿了?
  • Suspense 引发的样式丢失问题
  • 打包体积优化
  • 你好,GitHub Actions
  • 所见即所得
  • 静态方法之痛
  • SSR 静态样式导出
  • 依赖排查
  • 贡献者开发维护指南
  • 转载-如何提交无法解答的问题
  • 新的 Tooltip 对齐方式
  • 非必要的渲染
  • 如何成长为 Collaborator
  • Modal hook 的有趣 BUG
  • antd 测试库迁移的那些事儿
  • Tree 的勾选传导
  • getContainer 的一些变化
  • 组件级别的 CSS-in-JS
新的对齐方式
位置计算
缩放比
箭头优化
显示区域计算
总结

新的 Tooltip 对齐方式

2023-02-15
@zombieJ

文章被以下专栏收录:

antd

Ant Design

一个 UI 设计体系
我有想法,去参与讨论
antd

Ant Design

Ant Design 官方专栏
我有想法,去参与讨论
antd

Ant Design

Juejin logoAnt Design 开源专栏
Juejin logo我有想法,去参与讨论
文档贡献者
  • 转载-如何提交无法解答的问题非必要的渲染

    相关资源

    Ant Design X
    Ant Design Charts
    Ant Design Pro
    Pro Components
    Ant Design Mobile
    Ant Design Mini
    Ant Design Web3
    Ant Design Landing-首页模板集
    Scaffolds-脚手架市场
    Umi-React 应用开发框架
    dumi-组件/文档研发工具
    qiankun-微前端框架
    Ant Motion-设计动效
    国内镜像站点 🇨🇳

    社区

    Awesome Ant Design
    Medium
    Twitter
    yuque logoAnt Design 语雀专栏
    Ant Design 知乎专栏
    体验科技专栏
    seeconf logoSEE Conf-蚂蚁体验科技大会
    加入我们

    帮助

    GitHub
    更新日志
    常见问题
    报告 Bug
    议题
    讨论区
    StackOverflow
    SegmentFault

    Ant XTech logo更多产品

    yuque logo语雀-构建你的数字花园
    AntV logoAntV-数据可视化解决方案
    Egg logoEgg-企业级 Node.js 框架
    Kitchen logoKitchen-Sketch 工具集
    Galacean logoGalacean-互动图形解决方案
    xtech logo蚂蚁体验科技
    主题编辑器
    Made with ❤ by
    蚂蚁集团和 Ant Design 开源社区

    在 5.3.0 版本中,我们将会更新 Tooltip 组件的底层依赖 @rc-component/trigger 使其更好的实现自适应对齐逻辑。在此之前,我们先聊聊此前版本遇到的一些问题。

    滚动问题

    Tooltip 默认添加至 body 上,在全屏滚动时它会随着一起滚动。但是当 Tooltip 的目标元素放置在滚动容器中,则因为滚动容器不同而出现不会跟随滚动的情况:

    我们集中在 FAQ 提供了 getPopupContainer 的方式,让开发者将弹出元素通过该方法插入到目标元素的父级容器中,从而解决这个问题。但是这个方案并不完美,因为它需要开发者自己去判断目标元素的父级容器中哪个是滚动容器。在多层封装的组件中,可能使用 Tooltip 的组件与其滚动的组件并不是同一个,这使得设置目标滚动容器并不容易。

    贴边问题

    Tooltip 支持在滚动范围内贴边展示。但是由于弹出层是整体,使得居中的箭头在偏移后无法指向目标位置:

    我们会推荐使用 placement 属性,配置 topLeft 让弹出层靠左对齐来解决这个问题:

    同样的,如果是复用组件。可能它并不是总是需要贴边展示,当一个元素在中间展示时弹出层确实左/右对齐就会显得非常奇怪。

    缩放问题

    Tooltip 对齐底层使用 dom-align 库,它会直接为 DOM 节点添加 left | top | transform 样式来实现对齐,因而为了使其支持 React 生命周期,我们在此之上封装了 rc-align 组件。此外,它只关注对齐实现,本身不关注触发时机。所以 rc-align 组件还会额外添加 ResizeObserver 监听尺寸变化,继而调用 dom-align 进行对齐。

    dom-align 通过遍历父层节点累加计算出目标元素和弹出层各自的坐标位置,接着根据对齐规则计算差值。当父层节点有 transform 样式时,会导致计算出的坐标位置不准确,从而导致对齐不正确:

    新的对齐方式

    以上问题如滚动、贴边可以使用一些方式规避,而缩放问题则无法解决。我们希望这些问题可以由 antd 底层来解,而不是由开发者自己去处理。为此,我们重写了 @rc-component/trigger 组件,将对齐逻辑和箭头逻辑整合在一起。不再依赖 rc-align 以及 dom-align。同时使用新的计算方式避免 transform 样式导致的计算问题。

    位置计算

    考虑到弹出元素父层节点存在各种 position 的情况,递归查找父元素节点计算相对位置并不划算。我们只需要根据两者最终位置做偏移计算,再应用弹出层最终的缩放比例即可:

    1. 生成 Popup 元素
    2. 添加 Popup 样式 left: 0 & top: 0,将其强制对齐到左上角
      • Popup 元素父容器中 position 可能存在 fixed、relative、absolute节点,这都不影响我们计算偏移量。只要保证在 0/0 位置做偏移即可
    3. 通过 getBoundingClientRect 获取目标元素和 Popup 元素的位置信息
    4. 计算偏移差值
    Popup Align

    缩放比

    缩放比例无法直接获取,但是我们可以通过 getBoundingClientRect 与 offsetWidth/offsetHeight 计算出缩放比例:

    tsx
    const popupRect = popupEle.getBoundingClientRect();
    const { offsetWidth, offsetHeight } = popupEle;
    const scaleX = popupRect.width / offsetWidth;
    const scaleY = popupRect.height / offsetHeight;

    接着将缩放比例应用到计算出来的偏移值即可:

    tsx
    // Some logic for align offset calculation
    // const baseOffsetX = ...
    // const baseOffsetY = ...
    const scaledOffsetX = baseOffsetX / scaleX;
    const scaledOffsetY = baseOffsetY / scaleY;

    箭头优化

    在过去版本中,箭头由 rc-tooltip 添加而非 rc-trigger 管理。这使得 rc-tooltip 封装时已经丢失了对齐信息,以至于无法在 Popup 偏移时正确的调整箭头位置。为此,我们将箭头逻辑也整合到 rc-trigger 中,使得箭头的位置可以随着 Popup 的偏移而偏移。合并之后,箭头位置计算变得十分简单。我们只要取目标元素和 Popup 边界值最小值,再取中间值即可:

    居中定位

    center

    贴边定位

    center

    显示区域计算

    新的监听模式会在启动 Tooltip 时检测 Popup 父节点 overflow 样式,当存在 scroll、hidden、auto 时,叠加除滚动条外的可见区域,从而计算出最终的显示区域:

    VisibleRegion

    同样的,我们需要监听其滚动事件。任意父层节点滚动时,都需要重新计算显示区域:

    tsx
    function collectScroll(ele: HTMLElement) {
    const scrollList: HTMLElement[] = [];
    let current = ele?.parentElement;
    while (current) {
    if (isScrollContainer(current)) {
    scrollList.push(ele);
    }
    current = current.parentElement;
    }
    return scrollList;
    }
    const targetScrollList = collectScroll(targetEle);
    const popupScrollList = collectScroll(popupEle);
    // We merge the list in real world. Here just for sample
    [window, ...targetScrollList, ...popupScrollList].forEach((ele) => {
    ele.addEventListener(...);
    });

    最终,就得到了可以自适应滚动的效果:

    scroll

    总结

    在完成 Tooltip 改造后,我们还会继续改造其他使用到弹出层的组件。希望在此之后,开发者可以尽可能不需要再关注 getPopupContainer 的配置,而是可以直接使用组件。祝你开发愉快!