fix: prevent avatar fly-in and hide local player's own hover
Two critical UX fixes for hover avatars:
1. PREVENT FLY-IN FROM TOP-LEFT:
- Initialize position state as null instead of {x:0, y:0}
- Don't render avatar until position is calculated from card element
- Set immediate:true on first render to prevent animation
- Add opacity spring (0→1) for smooth fade-in
- Result: Avatar appears exactly where it should, no fly-in animation
2. HIDE YOUR OWN HOVER AVATAR:
- Filter out local player (isLocal === true) from hover avatar rendering
- Only show remote players' hover avatars
- You see opponents hovering, but not your own cursor
- Result: Cleaner UX, less visual noise on your own session
BEFORE:
- Avatar would fly in from (0,0) top-left corner when first hovering
- You'd see your own avatar following your mouse (distracting)
AFTER:
- Avatar appears smoothly at the correct card position
- Only remote players' avatars are visible
- Feels like watching someone else's cursor, not your own
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -92,7 +92,7 @@ function HoverAvatar({
|
||||
playerInfo: { emoji: string; name: string; color?: string }
|
||||
cardElement: HTMLElement | null
|
||||
}) {
|
||||
const [position, setPosition] = useState({ x: 0, y: 0 })
|
||||
const [position, setPosition] = useState<{ x: number; y: number } | null>(null)
|
||||
|
||||
// Update position when card element changes
|
||||
useEffect(() => {
|
||||
@@ -106,22 +106,29 @@ function HoverAvatar({
|
||||
}, [cardElement])
|
||||
|
||||
// Smooth spring animation for position changes
|
||||
// Only animate if we have a position (prevents fly-in from 0,0)
|
||||
const springProps = useSpring({
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
x: position?.x ?? 0,
|
||||
y: position?.y ?? 0,
|
||||
opacity: position ? 1 : 0, // Fade in when position is set
|
||||
config: {
|
||||
tension: 280,
|
||||
friction: 60,
|
||||
mass: 1,
|
||||
},
|
||||
immediate: !position, // No animation on first render
|
||||
})
|
||||
|
||||
// Don't render until we have a position
|
||||
if (!position) return null
|
||||
|
||||
return (
|
||||
<animated.div
|
||||
style={{
|
||||
position: 'fixed',
|
||||
left: springProps.x,
|
||||
top: springProps.y,
|
||||
opacity: springProps.opacity, // Fade in smoothly
|
||||
width: '48px',
|
||||
height: '48px',
|
||||
borderRadius: '50%',
|
||||
@@ -363,6 +370,11 @@ export function MemoryGrid() {
|
||||
{state.playerHovers &&
|
||||
Object.entries(state.playerHovers)
|
||||
.filter(([_, cardId]) => cardId !== null) // Only show if hovering a card
|
||||
.filter(([playerId]) => {
|
||||
// Don't show your own hover avatar (only show remote players)
|
||||
const player = playerMap.get(playerId)
|
||||
return player?.isLocal !== true
|
||||
})
|
||||
.map(([playerId, cardId]) => {
|
||||
const playerInfo = getPlayerHoverInfo(playerId)
|
||||
const cardElement = cardId ? cardRefs.current.get(cardId) : null
|
||||
|
||||
Reference in New Issue
Block a user