Browse Source

fix: fix viewport coords to use page coordinates

master
Muthu Kumar 1 month ago
parent
commit
f7d1573746
Failed to extract signature
  1. 45
      src/draggable.attempts/6/Draggable.ts
  2. 12
      src/draggable.attempts/6/Draggable2.tsx
  3. 260
      src/pages/main/Contact.tsx

45
src/draggable.attempts/6/Draggable.ts

@ -12,8 +12,8 @@ interface Vec2 {
export interface DraggableOpts { export interface DraggableOpts {
initialRotation?: number; initialRotation?: number;
onViewportExit?: () => void; onPageExit?: () => void;
onViewportEnter?: () => void; onPageEnter?: () => void;
} }
export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) { export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) {
@ -21,8 +21,8 @@ export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) {
const calculateInitialCenter = (): Vec2 => { const calculateInitialCenter = (): Vec2 => {
const rect = card.getBoundingClientRect(); const rect = card.getBoundingClientRect();
return { return {
x: rect.left + rect.width / 2, x: rect.left + rect.width / 2 + window.scrollX,
y: rect.top + rect.height / 2, y: rect.top + rect.height / 2 + window.scrollY,
}; };
}; };
@ -55,25 +55,29 @@ export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) {
let lastMousePosition: Vec2 = { x: 0, y: 0 }; let lastMousePosition: Vec2 = { x: 0, y: 0 };
let activePointerId: number | null = null; let activePointerId: number | null = null;
let animationFrameId: number | null = null; let animationFrameId: number | null = null;
let isOutsideViewport = false; let isOutsideBounds = false;
// --- Helpers --- // --- Helpers ---
const checkViewportExit = throttle(() => { const checkPageBounds = throttle(() => {
// Don't check if we're dragging, user may still be able to move the card back into view
if (state.dragging) return; if (state.dragging) return;
const rect = card.getBoundingClientRect(); const rect = card.getBoundingClientRect();
const pageLeft = rect.left + window.scrollX;
const pageTop = rect.top + window.scrollY;
const pageRight = rect.right + window.scrollX;
const pageBottom = rect.bottom + window.scrollY;
const outside = const outside =
rect.right < 0 || pageRight < 0 ||
rect.bottom < 0 || pageBottom < 0 ||
rect.left > window.innerWidth || pageLeft > document.documentElement.scrollWidth ||
rect.top > window.innerHeight; pageTop > document.documentElement.scrollHeight;
if (outside !== isOutsideViewport) { if (outside !== isOutsideBounds) {
isOutsideViewport = outside; isOutsideBounds = outside;
if (isOutsideViewport) opts.onViewportExit?.(); if (isOutsideBounds) opts.onPageExit?.();
else opts.onViewportEnter?.(); else opts.onPageEnter?.();
} }
}, VIEWPORT_CHECK_INTERVAL_MS); }, VIEWPORT_CHECK_INTERVAL_MS);
@ -158,10 +162,8 @@ export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) {
// 1. Store current visual state relative to the *old* initialCenter // 1. Store current visual state relative to the *old* initialCenter
const currentDeltaX = center.x - initialCenter.x; const currentDeltaX = center.x - initialCenter.x;
const currentDeltaY = center.y - initialCenter.y; const currentDeltaY = center.y - initialCenter.y;
const currentRotation = rotation; // Rotation doesn't depend on initialCenter
// 2. Temporarily remove the transform // 2. Temporarily remove the transform
card.style.transition = "none"; // Disable transitions during adjustment
card.style.transform = "none"; card.style.transform = "none";
// 3. Force browser reflow to get the *untouched* layout position // 3. Force browser reflow to get the *untouched* layout position
@ -180,10 +182,7 @@ export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) {
// 6. Reapply the transform immediately before the next paint // 6. Reapply the transform immediately before the next paint
// Use the *stored* delta and rotation to put it back visually where it was // Use the *stored* delta and rotation to put it back visually where it was
card.style.transform = `translate(${currentDeltaX}px, ${currentDeltaY}px) rotate(${currentRotation}rad)`; card.style.transform = `translate(${currentDeltaX}px, ${currentDeltaY}px) rotate(${rotation}rad)`;
// 7. Re-enable transitions if they were used
card.style.transition = ""; // Or restore previous transition style if needed
// The render loop will continue from this adjusted state. // The render loop will continue from this adjusted state.
}, RESIZE_DEBOUNCE_MS); // Apply debouncing }, RESIZE_DEBOUNCE_MS); // Apply debouncing
@ -231,7 +230,7 @@ export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) {
rotate(${rotation}rad) rotate(${rotation}rad)
`; `;
checkViewportExit(); checkPageBounds();
animationFrameId = requestAnimationFrame(render); animationFrameId = requestAnimationFrame(render);
} }

12
src/draggable.attempts/6/Draggable2.tsx

@ -5,8 +5,8 @@ import { css, cx } from "@emotion/css";
export type DraggableProps = React.HtmlHTMLAttributes<any> & { export type DraggableProps = React.HtmlHTMLAttributes<any> & {
as?: React.ElementType; as?: React.ElementType;
onViewportEnter?: () => void; onPageEnter?: () => void;
onViewportExit?: () => void; onPageExit?: () => void;
children: React.ReactNode; children: React.ReactNode;
initialRotation?: number; initialRotation?: number;
}; };
@ -17,8 +17,8 @@ export const Draggable = forwardRef<HTMLElement, DraggableProps>(
as: Comp = "div", as: Comp = "div",
children, children,
className, className,
onViewportEnter, onPageEnter,
onViewportExit, onPageExit,
initialRotation, initialRotation,
...props ...props
}: DraggableProps, }: DraggableProps,
@ -29,8 +29,8 @@ export const Draggable = forwardRef<HTMLElement, DraggableProps>(
useEffect(() => { useEffect(() => {
if (!cardRef.current) return; if (!cardRef.current) return;
return makeDraggable(cardRef.current, { return makeDraggable(cardRef.current, {
onViewportEnter, onPageEnter,
onViewportExit, onPageExit,
initialRotation, initialRotation,
}); });
}, []); }, []);

260
src/pages/main/Contact.tsx

@ -96,13 +96,7 @@ const Contact: React.FC = () => {
}, []); }, []);
return ( return (
<Container <Container>
className={css`
min-height: 50vh;
display: flex;
flex-direction: column;
position: relative;
`}>
<h1>MKRhere</h1> <h1>MKRhere</h1>
{visible < 1 && ( {visible < 1 && (
<AnimateEntry as="article" delay={500}> <AnimateEntry as="article" delay={500}>
@ -112,130 +106,146 @@ const Contact: React.FC = () => {
<a href="/">Start over?</a> <a href="/">Start over?</a>
</AnimateEntry> </AnimateEntry>
)} )}
{contactCards.map((rot, i) => ( <AnimateEntry
<Draggable as="main"
key={i} delay={200}
onViewportExit={() => setVisible(v => v - 1)} className={css`
onViewportEnter={() => setVisible(v => v + 1)} width: 100%;
initialRotation={rot} min-height: max(40vh, 11rem);
className={css` height: 100%;
width: 17rem; position: relative;
height: 11rem; `}>
font-size: 0.8rem; {contactCards.map((rot, i) => (
<Draggable
position: absolute; key={i}
bottom: 0; onPageExit={() => setVisible(v => v - 1)}
left: 0; onPageEnter={() => setVisible(v => v + 1)}
initialRotation={rot}
padding: 0; className={css`
background: transparent; width: 21rem;
`} height: 13rem;
ref={setupCursorTracking}> font-size: 1rem;
<Flippable
defaultFlipped={i !== contactCards.length - 1} @media screen and (max-width: 40rem) {
front={ width: 18rem;
<main height: 11rem;
className={css` font-size: 0.85rem;
height: 100%; }
width: 100%;
position: absolute;
overflow: hidden; bottom: 0;
border-radius: 0.5rem; left: 0;
background: var(--card-bg);
padding: 0;
box-shadow: 0 0 50rem 0 rgba(0, 0, 0, 0.8); background: transparent;
`}
display: flex; ref={setupCursorTracking}>
align-items: center; <Flippable
justify-content: center; defaultFlipped={i !== contactCards.length - 1}
gap: 1rem; front={
font-size: inherit; <main
className={css`
padding: 1rem 2.8em; height: 100%;
width: 100%;
ul {
padding: 0;
display: flex;
flex-direction: column;
gap: 0.5rem;
max-width: 50vw;
li {
list-style: none;
min-width: 4rem;
max-width: 100%;
}
li a { overflow: hidden;
display: block; border-radius: 0.5rem;
max-width: 100%; background: var(--card-bg);
white-space: nowrap;
text-overflow: ellipsis; box-shadow: 0 0 6rem 0 rgba(0, 0, 0, 0.7);
overflow: hidden;
}
/* Blog entry */ display: flex;
li:last-child { align-items: center;
margin-block-start: 1rem; justify-content: center;
gap: 1rem;
font-size: inherit;
padding: 1rem 2.8em;
ul {
padding: 0;
display: flex;
flex-direction: column;
gap: 0.5rem;
max-width: 50vw;
li {
list-style: none;
min-width: 4rem;
max-width: 100%;
}
li a {
display: block;
max-width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
/* Blog entry */
li:last-child {
margin-block-start: 1rem;
}
} }
}
`}>
<div className="dynamic-gradient" />
<ul
className={css`
text-align: right;
`}> `}>
{Object.keys(contact).map(key => ( <div className="dynamic-gradient" />
<li key={key}> <ul
<b>{key}.</b> className={css`
</li> text-align: right;
))} `}>
</ul> {Object.keys(contact).map(key => (
<ul>
{Object.keys(contact).map(key => {
const value = contact[key];
return (
<li key={key}> <li key={key}>
{value.link ? ( <b>{key}.</b>
<a
className={A}
href={value.link}
target="_blank"
rel="noreferrer"
style={{ width: "fit-content" }}>
{value.value}
</a>
) : (
value.value
)}
</li> </li>
); ))}
})} </ul>
</ul> <ul>
</main> {Object.keys(contact).map(key => {
} const value = contact[key];
back={
<main return (
className={css` <li key={key}>
width: 100%; {value.link ? (
height: 100%; <a
className={A}
overflow: hidden; href={value.link}
border-radius: 0.5rem; target="_blank"
background: var(--card-active); rel="noreferrer"
border: 1px solid var(--card-active-border); style={{ width: "fit-content" }}>
{value.value}
display: flex; </a>
align-items: center; ) : (
justify-content: center; value.value
`}> )}
<Logo width={100} /> </li>
</main> );
} })}
/> </ul>
</Draggable> </main>
))} }
back={
<main
className={css`
width: 100%;
height: 100%;
overflow: hidden;
border-radius: 0.5rem;
background: var(--card-active);
border: 1px solid var(--card-active-border);
display: flex;
align-items: center;
justify-content: center;
`}>
<Logo width={100} />
</main>
}
/>
</Draggable>
))}
</AnimateEntry>
</Container> </Container>
); );
}; };

Loading…
Cancel
Save