Skip to content

Commit 78d389f

Browse files
hashpandaclaude
andcommitted
fix(docs): 修复 TermTip 首次 hover 时 tooltip 位置跳动
添加 positioned 状态,先以 visibility:hidden 渲染 tooltip 测量尺寸, 在 requestAnimationFrame 中计算正确坐标后再设为可见并触发入场动画, 避免先以猜测坐标渲染再修正导致的视觉跳动。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9f4cd8f commit 78d389f

File tree

1 file changed

+18
-2
lines changed

1 file changed

+18
-2
lines changed

web/src/components/docs/term-tip.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ export function TermTip({ children }: { children: React.ReactNode }) {
2424
});
2525
const [coords, setCoords] = useState({ top: 0, left: 0 });
2626
const [placement, setPlacement] = useState<Placement>("bottom");
27+
// Whether position has been finalized after measuring the tooltip size.
28+
// Prevents the tooltip from flashing at a guessed position before the real
29+
// coordinates are computed.
30+
const [positioned, setPositioned] = useState(false);
2731

2832
useEffect(() => {
2933
setMounted(true);
@@ -73,21 +77,28 @@ export function TermTip({ children }: { children: React.ReactNode }) {
7377

7478
clearTimeout(hideTimerRef.current);
7579
triggerRef.current = el;
80+
setPositioned(false);
7681

7782
const rect = el.getBoundingClientRect();
7883
setCoords({ top: rect.bottom + 10, left: rect.left });
7984
setPlacement("bottom");
8085
setTooltip({ visible: true, text: tip });
8186

82-
requestAnimationFrame(() => updatePosition());
87+
requestAnimationFrame(() => {
88+
updatePosition();
89+
setPositioned(true);
90+
});
8391
},
8492
[updatePosition],
8593
);
8694

8795
const hide = useCallback(() => {
8896
hideTimerRef.current = setTimeout(() => {
8997
setTooltip({ visible: false, text: "" });
98+
setCoords({ top: 0, left: 0 });
99+
setPlacement("bottom");
90100
triggerRef.current = null;
101+
setPositioned(false);
91102
}, 150);
92103
}, []);
93104

@@ -141,9 +152,14 @@ export function TermTip({ children }: { children: React.ReactNode }) {
141152
position: "fixed",
142153
top: coords.top,
143154
left: coords.left,
155+
visibility: positioned ? "visible" : "hidden",
144156
}}
145157
initial={{ opacity: 0, y: placement === "bottom" ? -4 : 4 }}
146-
animate={{ opacity: 1, y: 0 }}
158+
animate={
159+
positioned
160+
? { opacity: 1, y: 0 }
161+
: { opacity: 0, y: placement === "bottom" ? -4 : 4 }
162+
}
147163
exit={{ opacity: 0 }}
148164
transition={{ duration: 0.15, ease: "easeOut" }}
149165
onMouseEnter={() => clearTimeout(hideTimerRef.current)}

0 commit comments

Comments
 (0)