修复提示框的弹出位置移动的问题

This commit is contained in:
2025-06-09 19:06:50 +08:00
parent 880e68d92c
commit 534e1ba153
8 changed files with 635 additions and 32 deletions
+60 -11
View File
@@ -60,6 +60,9 @@ export function Tooltip({
// 获取实际显示状态
const isVisible = isControlled ? controlledVisible : visible;
// 添加一个状态来控制实际渲染,解决位置跳跃问题
const [readyToShow, setReadyToShow] = useState(false);
// 引用DOM元素
const triggerRef = useRef<HTMLDivElement>(null);
const tooltipRef = useRef<HTMLDivElement>(null);
@@ -73,11 +76,17 @@ export function Tooltip({
if (!isControlled) {
setVisible(newVisible);
}
onVisibleChange?.(newVisible);
// 当显示状态变为false时,重置actualPlacement为初始placement
// 当显示状态变为false时,立即隐藏
if (!newVisible) {
setReadyToShow(false);
onVisibleChange?.(newVisible);
// 当显示状态变为false时,重置actualPlacement为初始placement
setActualPlacement(placement);
} else {
// 当显示状态变为true时,先延迟设置readyToShow
// 这样允许位置预计算完成后再显示tooltip
onVisibleChange?.(newVisible);
}
};
@@ -97,7 +106,6 @@ export function Tooltip({
// 获取触发元素位置
const triggerRect = triggerRef.current.getBoundingClientRect();
const tooltipRect = tooltipRef.current.getBoundingClientRect();
// 设置最大宽度
tooltipRef.current.style.maxWidth = typeof maxWidth === 'number'
@@ -115,6 +123,16 @@ export function Tooltip({
tooltipRef.current.style.overflow = 'visible';
}
// 强制重新计算布局,确保maxHeight生效后再获取尺寸
tooltipRef.current.style.visibility = 'hidden';
tooltipRef.current.style.display = 'block';
// 触发重排,确保maxHeight限制已应用
void tooltipRef.current.offsetHeight;
// 获取应用maxHeight后的实际tooltip尺寸
const tooltipRect = tooltipRef.current.getBoundingClientRect();
// 计算各个方向的位置
let top = 0, left = 0;
let arrowTop = 0, arrowLeft = 0;
@@ -311,6 +329,17 @@ export function Tooltip({
break;
}
}
// 恢复可见性(位置计算完成后)
tooltipRef.current.style.visibility = 'visible';
// 如果位置已更新且还没有显示,现在可以显示了
if (!readyToShow) {
// 使用requestAnimationFrame确保DOM更新后再显示
requestAnimationFrame(() => {
setReadyToShow(true);
});
}
};
// 当组件首次挂载或placement改变时,重置actualPlacement
@@ -318,12 +347,27 @@ export function Tooltip({
setActualPlacement(placement);
}, [placement]);
// 计算提示框位置
// 当isVisible状态变化时,处理初始化和清理工作
useEffect(() => {
if (!isVisible) return;
if (!isVisible) {
// 当tooltip隐藏时,重置readyToShow状态
setReadyToShow(false);
return;
}
// 初始化位置
updateTooltipPosition();
// 创建一个隐藏的tooltip用于预计算位置
// 使用一个隐藏的div来渲染tooltip,并获取其尺寸
const preCalculatePosition = () => {
// 初始时先不显示,让updateTooltipPosition完成位置计算
setReadyToShow(false);
// 初始化位置计算 - 添加短暂延迟确保内容已渲染
setTimeout(() => {
updateTooltipPosition();
}, 10);
};
preCalculatePosition();
// 添加滚动和调整大小事件监听器
window.addEventListener('scroll', updateTooltipPosition, true);
@@ -338,7 +382,7 @@ export function Tooltip({
window.removeEventListener('resize', updateTooltipPosition);
clearInterval(intervalId);
};
}, [isVisible, placement, maxWidth, showArrow, fixedPlacement, actualPlacement]);
}, [isVisible, placement, maxWidth, showArrow, fixedPlacement]);
// 处理点击外部关闭
useEffect(() => {
@@ -389,18 +433,23 @@ export function Tooltip({
`tooltip-${actualPlacement}`, // 使用actualPlacement而不是placement
`tooltip-${theme}`,
rich ? 'tooltip-rich' : '',
isVisible ? 'tooltip-visible' : '',
readyToShow ? 'tooltip-visible' : 'tooltip-hidden', // 使用readyToShow控制可见性
fixedPlacement ? 'tooltip-fixed-placement' : '',
className
].filter(Boolean).join(' ');
// 使用Portal渲染提示框
// 使用Portal渲染提示框,但根据readyToShow来控制可见性样式
const tooltipPortal = isVisible && typeof document !== 'undefined'
? createPortal(
<div
ref={tooltipRef}
className={tooltipClassNames}
style={{ maxWidth }}
style={{
maxWidth,
opacity: readyToShow ? 1 : 0, // 根据readyToShow控制透明度
visibility: readyToShow ? 'visible' : 'hidden', // 使用visibility确保在位置计算时元素存在但不可见
transition: 'opacity 0.1s ease-out' // 添加平滑过渡效果
}}
>
{renderTooltipContent()}
{showArrow && <div className="tooltip-arrow"></div>}