feat(flashcards): add grab point physics for realistic rotation
Implements physics-based rotation that responds to where the user grabs the card. When you grab a card off-center and drag it, it rotates naturally based on the grab point and drag direction. Features: - Calculate grab offset from card center on pointer down - Apply rotation using cross product of grab offset and drag direction - Rotation clamped to ±45° to prevent excessive spinning - Final rotation preserved when card is released - Console logging for grab point coordinates and rotation changes Physics details: - Cross product (grabOffset.x * deltaY - grabOffset.y * deltaX) determines rotation direction and magnitude - Grabbing left side + dragging right = clockwise rotation - Grabbing right side + dragging left = counter-clockwise rotation - Scale factor of 5000 provides smooth, realistic rotation feel 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
9f56c9728c
commit
bf37eb1928
|
|
@ -94,16 +94,19 @@ interface DraggableCardProps {
|
|||
function DraggableCard({ card }: DraggableCardProps) {
|
||||
// Track position - starts at initial, updates when dragged
|
||||
const [position, setPosition] = useState({ x: card.initialX, y: card.initialY })
|
||||
const [rotation] = useState(card.initialRotation)
|
||||
const [rotation, setRotation] = useState(card.initialRotation) // Now dynamic!
|
||||
const [zIndex, setZIndex] = useState(card.zIndex)
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
const [dragSpeed, setDragSpeed] = useState(0) // Speed for dynamic shadow
|
||||
|
||||
// Track drag state
|
||||
const dragStartRef = useRef<{ x: number; y: number; cardX: number; cardY: number } | null>(null)
|
||||
const grabOffsetRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 }) // Offset from card center where grabbed
|
||||
const baseRotationRef = useRef(card.initialRotation) // Starting rotation
|
||||
const lastMoveTimeRef = useRef<number>(0)
|
||||
const lastMovePositionRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 })
|
||||
const lastLogTimeRef = useRef<number>(0) // Separate throttling for logging
|
||||
const cardRef = useRef<HTMLDivElement>(null) // Reference to card element
|
||||
|
||||
const handlePointerDown = (e: React.PointerEvent) => {
|
||||
setIsDragging(true)
|
||||
|
|
@ -121,6 +124,23 @@ function DraggableCard({ card }: DraggableCardProps) {
|
|||
cardY: position.y,
|
||||
}
|
||||
|
||||
// Calculate grab offset from card center
|
||||
if (cardRef.current) {
|
||||
const rect = cardRef.current.getBoundingClientRect()
|
||||
const cardCenterX = rect.left + rect.width / 2
|
||||
const cardCenterY = rect.top + rect.height / 2
|
||||
grabOffsetRef.current = {
|
||||
x: e.clientX - cardCenterX,
|
||||
y: e.clientY - cardCenterY,
|
||||
}
|
||||
console.log(
|
||||
`[GrabPoint] Grabbed at offset: (${grabOffsetRef.current.x.toFixed(0)}, ${grabOffsetRef.current.y.toFixed(0)})px from center`
|
||||
)
|
||||
}
|
||||
|
||||
// Store the current rotation as the base for this drag
|
||||
baseRotationRef.current = rotation
|
||||
|
||||
// Initialize velocity tracking
|
||||
const now = Date.now()
|
||||
lastMoveTimeRef.current = now
|
||||
|
|
@ -166,6 +186,18 @@ function DraggableCard({ card }: DraggableCardProps) {
|
|||
lastMovePositionRef.current = { x: e.clientX, y: e.clientY }
|
||||
}
|
||||
|
||||
// Calculate rotation based on grab point physics
|
||||
// Cross product of grab offset and drag direction determines rotation
|
||||
// If grabbed on left and dragged right → clockwise rotation
|
||||
// If grabbed on right and dragged left → counter-clockwise rotation
|
||||
const crossProduct = grabOffsetRef.current.x * deltaY - grabOffsetRef.current.y * deltaX
|
||||
const rotationInfluence = crossProduct / 5000 // Scale factor for reasonable rotation (adjust as needed)
|
||||
const newRotation = baseRotationRef.current + rotationInfluence
|
||||
|
||||
// Clamp rotation to prevent excessive spinning
|
||||
const clampedRotation = Math.max(-45, Math.min(45, newRotation))
|
||||
setRotation(clampedRotation)
|
||||
|
||||
// Update card position
|
||||
setPosition({
|
||||
x: dragStartRef.current.cardX + deltaX,
|
||||
|
|
@ -178,6 +210,9 @@ function DraggableCard({ card }: DraggableCardProps) {
|
|||
dragStartRef.current = null
|
||||
|
||||
console.log('[Shadow] Drag released, speed decaying to 0')
|
||||
console.log(
|
||||
`[GrabPoint] Final rotation: ${rotation.toFixed(1)}° (base was ${baseRotationRef.current.toFixed(1)}°)`
|
||||
)
|
||||
|
||||
// Gradually decay speed back to 0 for smooth shadow transition
|
||||
const decayInterval = setInterval(() => {
|
||||
|
|
@ -205,6 +240,7 @@ function DraggableCard({ card }: DraggableCardProps) {
|
|||
|
||||
return (
|
||||
<div
|
||||
ref={cardRef}
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerMove={handlePointerMove}
|
||||
onPointerUp={handlePointerUp}
|
||||
|
|
|
|||
Loading…
Reference in New Issue