Browse Source

feat: I made the Draggables rotate

master
Muthu Kumar 1 month ago
parent
commit
b301d1a598
Failed to extract signature
  1. 114
      src/components/DraggableButton.tsx
  2. 14
      src/pages/main/Contact.tsx

114
src/components/DraggableButton.tsx

@ -9,6 +9,10 @@ interface Pos {
y: number; y: number;
} }
interface Velocity extends Pos {
timestamp: number;
}
const relativePos = (pos: Pos, container: DOMRect) => { const relativePos = (pos: Pos, container: DOMRect) => {
return { return {
x: pos.x - container.left, x: pos.x - container.left,
@ -21,17 +25,27 @@ export const DraggableButton = React.forwardRef<
DraggableButtonProps DraggableButtonProps
>(({ children, ...props }, ref) => { >(({ children, ...props }, ref) => {
const [position, setPosition] = useState({ x: 0, y: 0 }); const [position, setPosition] = useState({ x: 0, y: 0 });
const [rotation, setRotation] = useState(0);
const baseRotation = useRef(0);
const [isDragging, setIsDragging] = useState(false); const [isDragging, setIsDragging] = useState(false);
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 }); const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
const [isInitialized, setIsInitialized] = useState(false);
const myRef = useRef<HTMLButtonElement | null>(null); const myRef = useRef<HTMLButtonElement | null>(null);
const containerRef = useRef<DOMRect | null>(null); const containerRef = useRef<DOMRect | null>(null);
const [isInitialized, setIsInitialized] = useState(false); const lastVelocity = useRef<Velocity>({ x: 0, y: 0, timestamp: 0 });
const lastPosition = useRef<Pos>({ x: 0, y: 0 });
const animationFrame = useRef<number>();
// Convert initial position and set up element on mount // Capture initial rotation on mount
useEffect(() => { useEffect(() => {
const el = myRef.current; const el = myRef.current;
if (!el || isInitialized) return; if (!el || isInitialized) return;
// Extract initial rotation from transform style
const transform = window.getComputedStyle(el).transform;
const matrix = new DOMMatrix(transform);
baseRotation.current = Math.atan2(matrix.b, matrix.a) * (180 / Math.PI);
const rect = el.getBoundingClientRect(); const rect = el.getBoundingClientRect();
const parentRect = el.parentElement?.getBoundingClientRect() ?? rect; const parentRect = el.parentElement?.getBoundingClientRect() ?? rect;
@ -45,6 +59,7 @@ export const DraggableButton = React.forwardRef<
el.style.left = `${left}px`; el.style.left = `${left}px`;
el.style.bottom = "unset"; el.style.bottom = "unset";
el.style.right = "unset"; el.style.right = "unset";
el.style.transition = "none";
setPosition({ x: left, y: top }); setPosition({ x: left, y: top });
setIsInitialized(true); setIsInitialized(true);
@ -56,9 +71,10 @@ export const DraggableButton = React.forwardRef<
containerRef.current = el.parentElement?.getBoundingClientRect() ?? null; containerRef.current = el.parentElement?.getBoundingClientRect() ?? null;
el.style.transition = "none"; // Remove the transition property entirely
el.style.left = `${position.x}px`; el.style.left = `${position.x}px`;
el.style.top = `${position.y}px`; el.style.top = `${position.y}px`;
el.style.transform = `rotateZ(${rotation}deg)`;
const handleKeyDown = (e: KeyboardEvent) => { const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") setIsDragging(false); if (e.key === "Escape") setIsDragging(false);
@ -66,7 +82,7 @@ export const DraggableButton = React.forwardRef<
window.addEventListener("keydown", handleKeyDown); window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown);
}, [position, isInitialized]); }, [position, isInitialized, rotation, isDragging]);
const getEventPos = ( const getEventPos = (
e: React.MouseEvent | React.TouchEvent | MouseEvent | TouchEvent, e: React.MouseEvent | React.TouchEvent | MouseEvent | TouchEvent,
@ -86,61 +102,129 @@ export const DraggableButton = React.forwardRef<
const handleStart = (e: React.MouseEvent | React.TouchEvent) => { const handleStart = (e: React.MouseEvent | React.TouchEvent) => {
if (!myRef.current || !containerRef.current) return; if (!myRef.current || !containerRef.current) return;
// Prevent scrolling on touch devices
if ("touches" in e) { if ("touches" in e) {
e.preventDefault(); e.preventDefault();
} }
// Cancel any ongoing momentum animation
if (animationFrame.current) {
cancelAnimationFrame(animationFrame.current);
}
setIsDragging(true); setIsDragging(true);
const eventPos = getEventPos(e); const eventPos = getEventPos(e);
const relative = relativePos(eventPos, containerRef.current); const relative = relativePos(eventPos, containerRef.current);
// Initialize last position with current position
lastPosition.current = position;
lastVelocity.current = { x: 0, y: 0, timestamp: performance.now() };
setDragOffset({ setDragOffset({
x: relative.x - position.x, x: relative.x - position.x,
y: relative.y - position.y, y: relative.y - position.y,
}); });
}; };
const calculateRotation = (velocity: Velocity) => {
// Use a simpler rotation calculation based on position change
const ROTATION_FACTOR = 0.2; // Adjust this to make rotation more or less sensitive
// Use the actual position change for rotation
const positionDelta = lastPosition.current.x - position.x;
// Add to current rotation based on movement
return rotation + positionDelta * ROTATION_FACTOR;
};
const handleMove = (e: MouseEvent | TouchEvent) => { const handleMove = (e: MouseEvent | TouchEvent) => {
if (!isDragging || !containerRef.current) return; if (!isDragging || !containerRef.current) return;
// Prevent scrolling on touch devices
if ("touches" in e) { if ("touches" in e) {
e.preventDefault(); e.preventDefault();
} }
const eventPos = getEventPos(e); const eventPos = getEventPos(e);
const relative = relativePos(eventPos, containerRef.current); const relative = relativePos(eventPos, containerRef.current);
const newPosition = {
setPosition({
x: relative.x - dragOffset.x, x: relative.x - dragOffset.x,
y: relative.y - dragOffset.y, y: relative.y - dragOffset.y,
}); };
// Calculate velocity
const now = performance.now();
const elapsed = now - lastVelocity.current.timestamp;
if (elapsed > 0) {
const newVelocity = {
x: ((newPosition.x - lastPosition.current.x) / elapsed) * 16,
y: ((newPosition.y - lastPosition.current.y) / elapsed) * 16,
timestamp: now,
};
lastVelocity.current = newVelocity;
lastPosition.current = newPosition;
// Update rotation based on velocity
setRotation(calculateRotation(newVelocity));
}
setPosition(newPosition);
}; };
const handleEnd = () => { const handleEnd = () => {
setIsDragging(false); setIsDragging(false);
// Remove the rotation reset - let chaos reign!
// setRotation(baseRotation.current);
// Start momentum animation
if (
Math.abs(lastVelocity.current.x) > 0.1 ||
Math.abs(lastVelocity.current.y) > 0.1
) {
animationFrame.current = requestAnimationFrame(applyMomentum);
}
};
const applyMomentum = () => {
const now = performance.now();
const elapsed = now - lastVelocity.current.timestamp;
// Apply decay factor (0.95 = more momentum, 0.8 = less momentum)
const decay = Math.pow(0.7, elapsed / 16);
const newVelocity = {
x: lastVelocity.current.x * decay,
y: lastVelocity.current.y * decay,
timestamp: now,
};
// Stop animation when velocity is very low
if (Math.abs(newVelocity.x) < 0.01 && Math.abs(newVelocity.y) < 0.01) {
cancelAnimationFrame(animationFrame.current!);
return;
}
setPosition(prev => ({
x: prev.x + newVelocity.x,
y: prev.y + newVelocity.y,
}));
lastVelocity.current = newVelocity;
animationFrame.current = requestAnimationFrame(applyMomentum);
}; };
useEffect(() => { useEffect(() => {
if (isDragging) { if (isDragging) {
// Mouse events
window.addEventListener("mousemove", handleMove); window.addEventListener("mousemove", handleMove);
window.addEventListener("mouseup", handleEnd); window.addEventListener("mouseup", handleEnd);
// Touch events
window.addEventListener("touchmove", handleMove, { passive: false }); window.addEventListener("touchmove", handleMove, { passive: false });
window.addEventListener("touchend", handleEnd); window.addEventListener("touchend", handleEnd);
window.addEventListener("touchcancel", handleEnd); window.addEventListener("touchcancel", handleEnd);
} }
return () => { return () => {
// Mouse events
window.removeEventListener("mousemove", handleMove); window.removeEventListener("mousemove", handleMove);
window.removeEventListener("mouseup", handleEnd); window.removeEventListener("mouseup", handleEnd);
// Touch events
window.removeEventListener("touchmove", handleMove); window.removeEventListener("touchmove", handleMove);
window.removeEventListener("touchend", handleEnd); window.removeEventListener("touchend", handleEnd);
window.removeEventListener("touchcancel", handleEnd); window.removeEventListener("touchcancel", handleEnd);

14
src/pages/main/Contact.tsx

@ -91,10 +91,11 @@ const Home: React.FC = () => {
<h1>MKRhere</h1> <h1>MKRhere</h1>
<DraggableButton <DraggableButton
className={css` className={css`
bottom: 0rem; width: 22rem;
width: 20rem;
height: auto; height: auto;
aspect-ratio: 3 / 2; aspect-ratio: 3 / 2;
bottom: 0rem;
background: var(--card-tags); background: var(--card-tags);
border-radius: 0.5rem; border-radius: 0.5rem;
transform: rotateZ(5deg); transform: rotateZ(5deg);
@ -111,14 +112,17 @@ const Home: React.FC = () => {
</DraggableButton> </DraggableButton>
<DraggableButton <DraggableButton
className={css` className={css`
width: 22rem;
height: auto;
aspect-ratio: 3 / 2;
margin-top: auto; margin-top: auto;
display: flex; display: flex;
flex-shrink: 1; align-items: center;
justify-content: center;
gap: 1rem; gap: 1rem;
font-size: 1rem; font-size: 1rem;
width: fit-content;
height: fit-content;
padding: 1rem 2.8em; padding: 1rem 2.8em;
background: var(--card-bg); background: var(--card-bg);
border-radius: 0.5rem; border-radius: 0.5rem;

Loading…
Cancel
Save