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 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
extends React.ButtonHTMLAttributes<HTMLButtonElement> {}
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
onOutsideViewport?: () => void;
}
interface Pos {
x: number;
@ -23,7 +36,7 @@ const relativePos = (pos: Pos, container: DOMRect) => {
export const DraggableButton = React.forwardRef<
HTMLButtonElement,
DraggableButtonProps
>(({ children, ...props }, ref) => {
>(({ children, onOutsideViewport, ...props }, ref) => {
const [position, setPosition] = useState({ x: 0, y: 0 });
const [rotation, setRotation] = useState(0);
const [isDragging, setIsDragging] = useState(false);
@ -35,6 +48,12 @@ export const DraggableButton = React.forwardRef<
const lastPosition = useRef<Pos>({ x: 0, y: 0 });
const animationFrame = useRef<number>();
const [isOutside, setIsOutside] = useState(false);
useEffect(() => {
if (isOutside) onOutsideViewport?.();
}, [isOutside !== true]);
// Capture initial rotation on mount
useEffect(() => {
const el = myRef.current;
@ -70,6 +89,9 @@ export const DraggableButton = React.forwardRef<
el.style.top = `${position.y}px`;
el.style.rotate = `${rotation}deg`;
if (!isDragging && myRef.current && isOutsideViewport(myRef.current!))
setIsOutside(true);
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") setIsDragging(false);
};
@ -171,9 +193,10 @@ export const DraggableButton = React.forwardRef<
if (
Math.abs(lastVelocity.current.x) > 0.1 ||
Math.abs(lastVelocity.current.y) > 0.1
) {
)
animationFrame.current = requestAnimationFrame(applyMomentum);
}
if (isOutsideViewport(myRef.current!)) setIsOutside(true);
};
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 { DraggableButton } from "../../components/DraggableButton";
import { Flippable } from "../../components/Flippable";
import { AnimateEntry } from "../../components/AnimateEntry";
const A = css`
text-decoration: none;
@ -52,6 +53,7 @@ const cardRotations = Array.from({ length: 5 }, (_, i) => {
const Contact: React.FC = () => {
const [contact, setContact] = useState<Contact>(CONTACT);
const [visible, setVisible] = useState(cardRotations.length);
useEffect(() => {
const deob = () => {
@ -78,12 +80,14 @@ const Contact: React.FC = () => {
document.addEventListener("scroll", deob, { once: true });
document.addEventListener("click", deob, { once: true });
document.addEventListener("touchstart", deob, { once: true });
document.addEventListener("keydown", deob, { once: true });
return () => {
document.removeEventListener("mousemove", deob);
document.removeEventListener("scroll", deob);
document.removeEventListener("click", deob);
document.removeEventListener("touchstart", deob);
document.removeEventListener("keydown", deob);
};
}, []);
@ -96,9 +100,18 @@ const Contact: React.FC = () => {
position: relative;
`}>
<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) => (
<DraggableButton
key={i}
onOutsideViewport={() => setVisible(v => v - 1)}
className={css`
width: 22rem;
height: auto;

Loading…
Cancel
Save