在使用 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。
不仅:
- 更稳定
- 更流畅
- 样式更可控
而且: