Ant Design Slider Tooltip 的一个坑:页面抖动问题与自定义 Tooltip 方案

在使用 Ant Design 5.x 的 Slider 组件时,我遇到了一个问题:

快速拖动 Slider 时,页面会出现轻微抖动。

最开始以为是:

  • React 重渲染
  • Form 联动
  • CSS transition

导致的。

但最后发现:

真正的问题其实是 Slider 自带的 Tooltip。


问题复现

最简单的代码:

tsx 复制代码
<Slider />

拖动时页面会出现明显抖动。


原因分析

Ant Design Slider 的 Tooltip:

底层实际上使用的是:

txt 复制代码
rc-tooltip

Tooltip 默认:

会通过 Portal 渲染到 body。

也就是:

txt 复制代码
document.body

于是:

每次 Slider 拖动:

  • Tooltip 都会重新计算位置
  • 页面会触发 reflow
  • 页面会 repaint

从而导致:

页面抖动。


尝试过的方案

一开始尝试:

tsx 复制代码
<Slider
  tooltip={{
    getPopupContainer: (node) =>
      node?.parentElement || document.body,
  }}
/>

这样可以:

避免 Tooltip 挂到 body。

但仍然无法彻底解决问题。


最终解决方案

最后采用了:

完全禁用 Antd Tooltip

自己实现 Tooltip


为什么这样可以彻底解决?

因为:

我们绕过了:

txt 复制代码
rc-tooltip
rc-trigger
portal
dom-align

于是:

  • 不再挂 body
  • 不再动态计算 popup 位置
  • 不再触发页面 reflow

页面瞬间稳定。


实现思路

1. 禁用 Slider 默认 Tooltip

tsx 复制代码
tooltip={{
  open: false,
}}

2. 根据 Slider value 自己计算 Tooltip 位置

核心公式:

txt 复制代码
(value - min) / (max - min)

得到:

txt 复制代码
0 ~ 100%

的百分比。


完整实现(支持 Form)

下面是最终完整方案。

支持:

  • Form
  • initialValues
  • resetFields
  • validate
  • 拖动时显示 Tooltip
  • 停止拖动自动隐藏
  • 自定义白色主题

并且:

完全无页面抖动。


tsx 复制代码
import React, { useState } from 'react';
import { Form, Slider } from 'antd';

export default function App() {
  const [form] = Form.useForm();

  const [dragging, setDragging] = useState(false);

  const min = 0.3;
  const max = 1;

  return (
    <div
      style={{
        width: 500,
        padding: 40,
        background: '#fff',
      }}
    >
      <Form
        form={form}
        initialValues={{
          zoom: 0.5,
        }}
      >
        <Form.Item shouldUpdate noStyle>
          {() => {
            const value =
              form.getFieldValue('zoom') ?? min;

            // value 转百分比
            const percent =
              ((value - min) / (max - min)) * 100;

            return (
              <div
                style={{
                  position: 'relative',
                  paddingTop: 36,
                }}
              >
                {/* tooltip */}
                <div
                  style={{
                    position: 'absolute',

                    left: `${percent}%`,
                    top: 0,

                    transform:
                      'translateX(-50%)',

                    minWidth: 40,
                    height: 28,

                    padding: '0 10px',

                    background: '#fff',
                    color: '#000',

                    border:
                      '1px solid #d9d9d9',

                    borderRadius: 6,

                    boxShadow:
                      '0 2px 8px rgba(0,0,0,0.15)',

                    fontSize: 12,

                    display: 'flex',
                    alignItems: 'center',
                    justifyContent: 'center',

                    whiteSpace: 'nowrap',

                    pointerEvents: 'none',

                    opacity: dragging ? 1 : 0,

                    visibility: dragging
                      ? 'visible'
                      : 'hidden',

                    transition:
                      'opacity 0.15s',

                    zIndex: 10,
                  }}
                >
                  {value.toFixed(1)}
                </div>

                {/* 外层边框三角 */}
                <div
                  style={{
                    position: 'absolute',

                    left: `${percent}%`,
                    top: 28,

                    transform:
                      'translateX(-50%)',

                    width: 0,
                    height: 0,

                    borderLeft:
                      '6px solid transparent',

                    borderRight:
                      '6px solid transparent',

                    borderTop:
                      '7px solid #d9d9d9',

                    opacity: dragging ? 1 : 0,

                    visibility: dragging
                      ? 'visible'
                      : 'hidden',
                  }}
                />

                {/* 内层白色三角 */}
                <div
                  style={{
                    position: 'absolute',

                    left: `${percent}%`,
                    top: 27,

                    transform:
                      'translateX(-50%)',

                    width: 0,
                    height: 0,

                    borderLeft:
                      '5px solid transparent',

                    borderRight:
                      '5px solid transparent',

                    borderTop:
                      '6px solid #fff',

                    opacity: dragging ? 1 : 0,

                    visibility: dragging
                      ? 'visible'
                      : 'hidden',
                  }}
                />

                <Form.Item
                  name="zoom"
                  noStyle
                >
                  <Slider
                    min={min}
                    max={max}
                    step={0.1}
                    tooltip={{
                      open: false,
                    }}
                    onChange={() => {
                      setDragging(true);
                    }}
                    onAfterChange={() => {
                      setDragging(false);
                    }}
                  />
                </Form.Item>
              </div>
            );
          }}
        </Form.Item>
      </Form>
    </div>
  );
}

几个关键点


1. Tooltip 必须使用 transform 居中

正确写法:

tsx 复制代码
left: `${percent}%`,
transform: 'translateX(-50%)',

这样:

Tooltip 会始终与 Slider Handle 中心对齐。


2. 不要使用条件渲染

很多人会写:

tsx 复制代码
{dragging && <Tooltip />}

但:

这样会触发 DOM 卸载与重新布局。

更稳的方案:

tsx 复制代码
opacity + visibility

即:

tsx 复制代码
opacity: dragging ? 1 : 0,
visibility: dragging ? 'visible' : 'hidden',

3. 为什么使用双层三角?

因为 Tooltip 使用的是白色背景。

如果只有:

css 复制代码
borderTop: white

会与背景融为一体。

所以:

需要:

  • 外层灰色边框三角
  • 内层白色填充三角

才能形成:

清晰的悬浮感。


总结

Ant Design Slider 默认 Tooltip:

虽然使用方便。

但在某些场景下:

很容易导致页面抖动。

真正稳定的方案:

往往不是:

"继续调整 Tooltip 配置"。

而是:

彻底绕过 Tooltip 系统。

自己实现一个轻量级 Tooltip。

不仅:

  • 更稳定
  • 更流畅
  • 样式更可控

而且:

性能也会更好。

相关推荐
智商不够_熬夜来凑14 小时前
【Timeline】
前端·javascript·vue.js
杨运交15 小时前
[024][Web模块]基于 AntiSamy 的 Spring Boot XSS 防护实践:从过滤器到反序列化的多层防御
前端·spring boot·xss
学点程序15 小时前
HyperFrames:用 HTML 生成视频的开源渲染框架
前端·开源·html·音视频
zhangxingchao15 小时前
AI 大模型核心五:从 Transformer、RAG 到 Agent 架构
前端·人工智能·后端
昭昭颂桉a15 小时前
Tailwind CSS 完全指南 —— 从零到一,告别手写 CSS
前端·css
英俊潇洒美少年1 天前
Vue 生产环境打包:SourceMap、压缩、混淆、加密全解 + 最佳实践
前端·javascript·vue.js
巴博尔1 天前
UNIAPP中NVUE页面 动画
android·前端·javascript·ios·uni-app
她说人狗殊途1 天前
基于 vue-cli 创建
前端·javascript·vue.js