fix: correct avatar positioning to prevent fly-in animation
Fixed positioning model to work properly with react-spring: ISSUE: - Avatar was still flying in from top-right - Using translate(-50%, -50%) with animated left/top caused positioning issues - Spring was animating from wrong initial position SOLUTION: - Remove transform: translate(-50%, -50%) - Use negative margins instead: marginLeft: -24px, marginTop: -24px - Calculate exact center position from getBoundingClientRect - Set 'from' position in spring to match 'to' on first render - Use x.to() and y.to() to convert spring values to px strings POSITIONING MODEL: 1. Get card's bounding rect 2. Calculate avatar center point: (rect.right - 12, rect.top - 12) 3. Apply via left/top with negative margins for centering 4. Spring animates between positions smoothly 5. On first hover: from/to are same (no animation) 6. On subsequent hovers: smooth glide between cards RESULT: Avatar appears exactly at the card position with no fly-in animation, then smoothly glides to new positions as user moves between cards. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -98,25 +98,35 @@ function HoverAvatar({
|
||||
useEffect(() => {
|
||||
if (cardElement) {
|
||||
const rect = cardElement.getBoundingClientRect()
|
||||
// Calculate the actual position we want the avatar centered at (top-right of card)
|
||||
// Since we're using translate(-50%, -50%), we need the center point
|
||||
const avatarSize = 48
|
||||
const avatarCenterX = rect.right - 12 // 12px from right edge
|
||||
const avatarCenterY = rect.top - 12 // 12px from top edge
|
||||
|
||||
setPosition({
|
||||
x: rect.right - 12, // Position at top-right of card
|
||||
y: rect.top - 12,
|
||||
x: avatarCenterX,
|
||||
y: avatarCenterY,
|
||||
})
|
||||
}
|
||||
}, [cardElement])
|
||||
|
||||
// Smooth spring animation for position changes
|
||||
// Only animate if we have a position (prevents fly-in from 0,0)
|
||||
// Use 'from' to set initial position when avatar first appears
|
||||
const springProps = useSpring({
|
||||
x: position?.x ?? 0,
|
||||
y: position?.y ?? 0,
|
||||
opacity: position ? 1 : 0, // Fade in when position is set
|
||||
from: position
|
||||
? { x: position.x, y: position.y, opacity: 0 }
|
||||
: { x: 0, y: 0, opacity: 0 },
|
||||
to: {
|
||||
x: position?.x ?? 0,
|
||||
y: position?.y ?? 0,
|
||||
opacity: position ? 1 : 0,
|
||||
},
|
||||
config: {
|
||||
tension: 280,
|
||||
friction: 60,
|
||||
mass: 1,
|
||||
},
|
||||
immediate: !position, // No animation on first render
|
||||
})
|
||||
|
||||
// Don't render until we have a position
|
||||
@@ -126,11 +136,14 @@ function HoverAvatar({
|
||||
<animated.div
|
||||
style={{
|
||||
position: 'fixed',
|
||||
left: springProps.x,
|
||||
top: springProps.y,
|
||||
opacity: springProps.opacity, // Fade in smoothly
|
||||
// Don't use translate, just position directly at the calculated point
|
||||
left: springProps.x.to((x) => `${x}px`),
|
||||
top: springProps.y.to((y) => `${y}px`),
|
||||
opacity: springProps.opacity,
|
||||
width: '48px',
|
||||
height: '48px',
|
||||
marginLeft: '-24px', // Center horizontally (half of width)
|
||||
marginTop: '-24px', // Center vertically (half of height)
|
||||
borderRadius: '50%',
|
||||
background: playerInfo.color || 'linear-gradient(135deg, #667eea, #764ba2)',
|
||||
display: 'flex',
|
||||
@@ -143,7 +156,6 @@ function HoverAvatar({
|
||||
border: '3px solid white',
|
||||
zIndex: 1000,
|
||||
pointerEvents: 'none',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
filter: 'drop-shadow(0 0 8px rgba(102, 126, 234, 0.8))',
|
||||
}}
|
||||
className={css({
|
||||
|
||||
Reference in New Issue
Block a user