Commit abf327ab by luoqi

fix(web): hover Portal 防裁切 + 桌宠套绳距离上限

HoverCardContent 包 Portal + collisionPadding,优先级浮层逃出侧栏 overflow:hidden
不再被裁。桌宠套绳手势加距离上限(宠物鼠标 ≤ 半屏高),超距不触发。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parent 4d4339ab
......@@ -241,13 +241,18 @@ export function usePetLocomotion(
/** 套绳荡飞:绳子套在锚点(默认 = 当前鼠标位置),钟摆荡 1.5 个来回后松手甩飞。 */
/** 套绳就绪:鼠标在视窗内、且离宠物足够远(锚点可在任意方位 —— 上/下/侧都行)。 */
const swingReady = () =>
c.cur.t > 0 &&
c.cur.x >= 0 &&
c.cur.x <= window.innerWidth &&
c.cur.y >= 0 &&
c.cur.y <= window.innerHeight &&
Math.hypot(c.cur.x - (c.x + size / 2), c.cur.y - (c.y + size / 2)) > 110;
const swingReady = () => {
const d = Math.hypot(c.cur.x - (c.x + size / 2), c.cur.y - (c.y + size / 2));
return (
c.cur.t > 0 &&
c.cur.x >= 0 &&
c.cur.x <= window.innerWidth &&
c.cur.y >= 0 &&
c.cur.y <= window.innerHeight &&
d > 110 &&
d <= window.innerHeight / 2 // 距离上限:宠物↔鼠标 ≤ 半屏高,过远不套绳
);
};
const startSwing = (anchor?: { x: number; y: number }) => {
const pcx = c.x + size / 2;
......@@ -348,11 +353,14 @@ export function usePetLocomotion(
cr.minY = Math.min(cr.minY, e.clientY);
cr.maxY = Math.max(cr.maxY, e.clientY);
const diag = Math.hypot(cr.maxX - cr.minX, cr.maxY - cr.minY);
// 宠物↔鼠标(画圈处)距离上限:半个屏幕高度,过远不触发套绳
const petMouseDist = Math.hypot(e.clientX - (c.x + size / 2), e.clientY - (c.y + size / 2));
if (
Math.abs(cr.accum) > 5.8 && // ≈ 330°
cr.len > 140 &&
diag > 30 &&
diag < 650 &&
petMouseDist <= window.innerHeight / 2 &&
now - cr.lastAt > 6_000 &&
c.motion === 'rest' &&
!getBusy()
......
......@@ -13,21 +13,25 @@ const HoverCardContent = React.forwardRef<
React.ElementRef<typeof HoverCardPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
<HoverCardPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
'z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none',
'data-[state=open]:animate-in data-[state=closed]:animate-out',
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2',
'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className,
)}
{...props}
/>
// ⭐ Portal:渲染到 body,逃出祖先 overflow:hidden 裁切(优先级/画像 hover 浮层被侧栏切掉的根因)
<HoverCardPrimitive.Portal>
<HoverCardPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
collisionPadding={8}
className={cn(
'z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none',
'data-[state=open]:animate-in data-[state=closed]:animate-out',
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2',
'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className,
)}
{...props}
/>
</HoverCardPrimitive.Portal>
));
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment