Browse Source

feat: isOutsideViewport 🐰🥚

master
Muthu Kumar 1 month ago
parent
commit
2a1c3f4eab
Failed to extract signature
  1. 31
      src/components/DraggableButton.tsx
  2. 13
      src/pages/main/Contact.tsx

31
src/components/DraggableButton.tsx

@ -1,8 +1,21 @@
import { css, cx } from "@emotion/css"; import { css, cx } from "@emotion/css";
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
const isOutsideViewport = (el: HTMLElement) => {
const rect = el.getBoundingClientRect();
const isOutside =
rect.right < 0 ||
rect.left > window.innerWidth ||
rect.bottom < 0 ||
rect.top > window.innerHeight;
return isOutside;
};
export interface DraggableButtonProps export interface DraggableButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {} extends React.ButtonHTMLAttributes<HTMLButtonElement> {
onOutsideViewport?: () => void;
}
interface Pos { interface Pos {
x: number; x: number;
@ -23,7 +36,7 @@ const relativePos = (pos: Pos, container: DOMRect) => {
export const DraggableButton = React.forwardRef< export const DraggableButton = React.forwardRef<
HTMLButtonElement, HTMLButtonElement,
DraggableButtonProps DraggableButtonProps
>(({ children, ...props }, ref) => { >(({ children, onOutsideViewport, ...props }, ref) => {
const [position, setPosition] = useState({ x: 0, y: 0 }); const [position, setPosition] = useState({ x: 0, y: 0 });
const [rotation, setRotation] = useState(0); const [rotation, setRotation] = useState(0);
const [isDragging, setIsDragging] = useState(false); const [isDragging, setIsDragging] = useState(false);
@ -35,6 +48,12 @@ export const DraggableButton = React.forwardRef<
const lastPosition = useRef<Pos>({ x: 0, y: 0 }); const lastPosition = useRef<Pos>({ x: 0, y: 0 });
const animationFrame = useRef<number>(); const animationFrame = useRef<number>();
const [isOutside, setIsOutside] = useState(false);
useEffect(() => {
if (isOutside) onOutsideViewport?.();
}, [isOutside !== true]);
// Capture initial rotation on mount // Capture initial rotation on mount
useEffect(() => { useEffect(() => {
const el = myRef.current; const el = myRef.current;
@ -70,6 +89,9 @@ export const DraggableButton = React.forwardRef<
el.style.top = `${position.y}px`; el.style.top = `${position.y}px`;
el.style.rotate = `${rotation}deg`; el.style.rotate = `${rotation}deg`;
if (!isDragging && myRef.current && isOutsideViewport(myRef.current!))
setIsOutside(true);
const handleKeyDown = (e: KeyboardEvent) => { const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") setIsDragging(false); if (e.key === "Escape") setIsDragging(false);
}; };
@ -171,9 +193,10 @@ export const DraggableButton = React.forwardRef<
if ( if (
Math.abs(lastVelocity.current.x) > 0.1 || Math.abs(lastVelocity.current.x) > 0.1 ||
Math.abs(lastVelocity.current.y) > 0.1 Math.abs(lastVelocity.current.y) > 0.1
) { )
animationFrame.current = requestAnimationFrame(applyMomentum); animationFrame.current = requestAnimationFrame(applyMomentum);
}
if (isOutsideViewport(myRef.current!)) setIsOutside(true);
}; };
const applyMomentum = () => { const applyMomentum = () => {

13
src/pages/main/Contact.tsx

@ -6,6 +6,7 @@ import { setupCursorTracking } from "../../util";
import { ReactComponent as Logo } from "../../assets/logo.svg"; import { ReactComponent as Logo } from "../../assets/logo.svg";
import { DraggableButton } from "../../components/DraggableButton"; import { DraggableButton } from "../../components/DraggableButton";
import { Flippable } from "../../components/Flippable"; import { Flippable } from "../../components/Flippable";
import { AnimateEntry } from "../../components/AnimateEntry";
const A = css` const A = css`
text-decoration: none; text-decoration: none;
@ -52,6 +53,7 @@ const cardRotations = Array.from({ length: 5 }, (_, i) => {
const Contact: React.FC = () => { const Contact: React.FC = () => {
const [contact, setContact] = useState<Contact>(CONTACT); const [contact, setContact] = useState<Contact>(CONTACT);
const [visible, setVisible] = useState(cardRotations.length);
useEffect(() => { useEffect(() => {
const deob = () => { const deob = () => {
@ -78,12 +80,14 @@ const Contact: React.FC = () => {
document.addEventListener("scroll", deob, { once: true }); document.addEventListener("scroll", deob, { once: true });
document.addEventListener("click", deob, { once: true }); document.addEventListener("click", deob, { once: true });
document.addEventListener("touchstart", deob, { once: true }); document.addEventListener("touchstart", deob, { once: true });
document.addEventListener("keydown", deob, { once: true });
return () => { return () => {
document.removeEventListener("mousemove", deob); document.removeEventListener("mousemove", deob);
document.removeEventListener("scroll", deob); document.removeEventListener("scroll", deob);
document.removeEventListener("click", deob); document.removeEventListener("click", deob);
document.removeEventListener("touchstart", deob); document.removeEventListener("touchstart", deob);
document.removeEventListener("keydown", deob);
}; };
}, []); }, []);
@ -96,9 +100,18 @@ const Contact: React.FC = () => {
position: relative; position: relative;
`}> `}>
<h1>MKRhere</h1> <h1>MKRhere</h1>
{visible < 1 && (
<AnimateEntry as="article" delay={500}>
<p>Great, You've distributed all the cards!</p>
<p>What now?</p>
<br />
<a href="/">Start over?</a>
</AnimateEntry>
)}
{cardRotations.map((rot, i) => ( {cardRotations.map((rot, i) => (
<DraggableButton <DraggableButton
key={i} key={i}
onOutsideViewport={() => setVisible(v => v - 1)}
className={css` className={css`
width: 22rem; width: 22rem;
height: auto; height: auto;

Loading…
Cancel
Save