Skip to content

Commit

Permalink
feat: pinch and drag 테스트 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
Yoon-Hae-Min committed Aug 4, 2024
1 parent ed0513c commit f0dd33a
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 9 deletions.
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@
},
"dependencies": {
"@radix-ui/react-popover": "^1.1.1",
"@react-spring/web": "^9.7.4",
"@use-gesture/react": "^10.3.1",
"autoprefixer": "^10.4.19",
"class-variance-authority": "^0.7.0",
"next": "14.2.5",
"react": "^18",
"react-dom": "^18",
"tailwind-scrollbar-hide": "^1.1.7",
"tailwindcss-animate": "^1.0.7"
"tailwindcss-animate": "^1.0.7",
"ts-pattern": "^5.2.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/node": "^20",
Expand Down
6 changes: 5 additions & 1 deletion src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,8 @@
body {
@apply bg-background text-foreground;
}
}
html,
body {
touch-action: none;
}
}
103 changes: 96 additions & 7 deletions src/app/mobile/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use client';

import { animated, useSpring } from '@react-spring/web';
import { createUseGesture, dragAction, pinchAction } from '@use-gesture/react';
import Image from 'next/image';
import React from 'react';

Expand All @@ -8,9 +10,83 @@ import { cn } from '@/lib/utils';

import { ResortList, Spot } from './data';

const useGesture = createUseGesture([pinchAction, dragAction]);

const Page = () => {
const [selectedTab, setSelectedTab] = React.useState(ResortList[0]);
const [selectedSpot, setSelectedSpot] = React.useState<Spot | null>(null);
const [style, api] = useSpring(() => ({ scale: 1, x: 0, y: 0 }));
const ref = React.useRef<HTMLDivElement>(null);

useGesture(
{
onPinch: ({ origin: [ox, oy], first, movement: [ms], offset: [s], memo }) => {
if (first) {
const { width, height, x, y } = ref.current!.getBoundingClientRect();
const tx = ox - (x + width / 2);
const ty = oy - (y + height / 2);
memo = [style.x.get(), style.y.get(), tx, ty];
}

const x = memo[0] - (ms - 1) * memo[2];
const y = memo[1] - (ms - 1) * memo[3];
api.start({ scale: s, x, y });
return memo;
},
onPinchEnd: () => {
if (style.scale.get() < 1) {
api.start({ scale: 1, x: 0, y: 0 });
}
},
onDrag: ({ pinching, cancel, offset: [x, y] }) => {
if (pinching) return cancel();
api.start({ x, y });
},
onDragEnd: () => {
const [boundedX, boundedY] = getBoundedPositions(
style.x.get(),
style.y.get(),
style.scale.get()
);
api.start({ x: boundedX, y: boundedY });
},
},
{
target: ref,
drag: { from: () => [style.x.get(), style.y.get()] },
pinch: { scaleBounds: { min: 1, max: 6 }, rubberband: true },
}
);

const getBoundedPositions = (x: number, y: number, scale: number): [number, number] => {
const CONTAINER = { width: 376, height: 200 };
const IMAGE = { width: 376, height: 357 };
const OFFSET_Y = IMAGE.height - CONTAINER.height;

const scaledSize = {
width: IMAGE.width * scale,
height: IMAGE.height * scale,
};

const bounds = {
x: {
min: (CONTAINER.width - scaledSize.width) / 2,
max: -(CONTAINER.width - scaledSize.width) / 2,
},
y: {
min: -((scaledSize.height - CONTAINER.height + OFFSET_Y) / 2),
max: Math.max((scaledSize.height - CONTAINER.height - OFFSET_Y) / 2, 0),
},
};

const boundedPosition = {
x: Math.max(bounds.x.min, Math.min(bounds.x.max, x)),
y: Math.max(bounds.y.min, Math.min(bounds.y.max, y)),
};

return [boundedPosition.x, boundedPosition.y];
};

return (
<div className={cn('size-full')}>
<div className={cn('mb-1 flex w-full overflow-scroll scrollbar-hide')}>
Expand All @@ -32,13 +108,26 @@ const Page = () => {
</div>
<Summary {...ResortList.find((tab) => tab.name === selectedTab.name)!} />
<div className={cn('relative h-[200px] w-full overflow-hidden')}>
<Image
className={cn('object-cover')}
width={376}
height={200}
src={`/map/${selectedTab.map}`}
alt={`${selectedTab.name}`}
/>
<animated.div
ref={ref}
style={{
touchAction: 'none',
display: 'inline-block',
...style,
}}
>
<Image
className={cn('object-cover')}
width={376}
height={357}
src={`/map/${selectedTab.map}`}
alt={`${selectedTab.name}`}
/>
<div
className={cn('absolute left-0 top-3 h-2 w-3 bg-black opacity-20')}
onClick={() => console.log('hi')}
/>
</animated.div>
{selectedSpot && (
<div className={cn('absolute left-0 top-0 size-full')}>
<video src={`/video/${selectedTab.tag}/${selectedSpot.tag}.mov`} muted autoPlay loop />
Expand Down
67 changes: 67 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,51 @@
resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.0.tgz#f817d1d3265ac5415dadc67edab30ae196696438"
integrity sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==

"@react-spring/animated@~9.7.4":
version "9.7.4"
resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.7.4.tgz#c712b2d3dc9312ef41aa8886818b539151bda062"
integrity sha512-7As+8Pty2QlemJ9O5ecsuPKjmO0NKvmVkRR1n6mEotFgWar8FKuQt2xgxz3RTgxcccghpx1YdS1FCdElQNexmQ==
dependencies:
"@react-spring/shared" "~9.7.4"
"@react-spring/types" "~9.7.4"

"@react-spring/core@~9.7.4":
version "9.7.4"
resolved "https://registry.yarnpkg.com/@react-spring/core/-/core-9.7.4.tgz#0eaa0b5da3d18036d87a571f23079819d45a9f46"
integrity sha512-GzjA44niEJBFUe9jN3zubRDDDP2E4tBlhNlSIkTChiNf9p4ZQlgXBg50qbXfSXHQPHak/ExYxwhipKVsQ/sUTw==
dependencies:
"@react-spring/animated" "~9.7.4"
"@react-spring/shared" "~9.7.4"
"@react-spring/types" "~9.7.4"

"@react-spring/rafz@~9.7.4":
version "9.7.4"
resolved "https://registry.yarnpkg.com/@react-spring/rafz/-/rafz-9.7.4.tgz#d53aa45a8cb116b81b27ba29e0cc15470ccfd449"
integrity sha512-mqDI6rW0Ca8IdryOMiXRhMtVGiEGLIO89vIOyFQXRIwwIMX30HLya24g9z4olDvFyeDW3+kibiKwtZnA4xhldA==

"@react-spring/shared@~9.7.4":
version "9.7.4"
resolved "https://registry.yarnpkg.com/@react-spring/shared/-/shared-9.7.4.tgz#8ac57505072c2aee33d77c47c4269347061a3377"
integrity sha512-bEPI7cQp94dOtCFSEYpxvLxj0+xQfB5r9Ru1h8OMycsIq7zFZon1G0sHrBLaLQIWeMCllc4tVDYRTLIRv70C8w==
dependencies:
"@react-spring/rafz" "~9.7.4"
"@react-spring/types" "~9.7.4"

"@react-spring/types@~9.7.4":
version "9.7.4"
resolved "https://registry.yarnpkg.com/@react-spring/types/-/types-9.7.4.tgz#c849a7f062b5163d078e5e75f28c8f6acf91792e"
integrity sha512-iQVztO09ZVfsletMiY+DpT/JRiBntdsdJ4uqk3UJFhrhS8mIC9ZOZbmfGSRs/kdbNPQkVyzucceDicQ/3Mlj9g==

"@react-spring/web@^9.7.4":
version "9.7.4"
resolved "https://registry.yarnpkg.com/@react-spring/web/-/web-9.7.4.tgz#0086ab5dcf17e6a8f3d7e7f8041ccb4cc2fa10dc"
integrity sha512-UMvCZp7I5HCVIleSa4BwbNxynqvj+mJjG2m20VO2yPoi2pnCYANy58flvz9v/YcXTAvsmL655FV3pm5fbr6akA==
dependencies:
"@react-spring/animated" "~9.7.4"
"@react-spring/core" "~9.7.4"
"@react-spring/shared" "~9.7.4"
"@react-spring/types" "~9.7.4"

"@rushstack/eslint-patch@^1.3.3":
version "1.10.3"
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.10.3.tgz#391d528054f758f81e53210f1a1eebcf1a8b1d20"
Expand Down Expand Up @@ -477,6 +522,18 @@
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==

"@use-gesture/[email protected]":
version "10.3.1"
resolved "https://registry.yarnpkg.com/@use-gesture/core/-/core-10.3.1.tgz#976c9421e905f0079d49822cfd5c2e56b808fc56"
integrity sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==

"@use-gesture/react@^10.3.1":
version "10.3.1"
resolved "https://registry.yarnpkg.com/@use-gesture/react/-/react-10.3.1.tgz#17a743a894d9bd9a0d1980c618f37f0164469867"
integrity sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==
dependencies:
"@use-gesture/core" "10.3.1"

acorn-jsx@^5.3.2:
version "5.3.2"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
Expand Down Expand Up @@ -2905,6 +2962,11 @@ ts-interface-checker@^0.1.9:
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==

ts-pattern@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/ts-pattern/-/ts-pattern-5.2.0.tgz#2cad8b58fcd87c52d1785f84eba572641e1bb5f3"
integrity sha512-aGaSpOlDcns7ZoeG/OMftWyQG1KqPVhgplhJxNCvyIXqWrumM5uIoOSarw/hmmi/T1PnuQ/uD8NaFHvLpHicDg==

tsconfig-paths@^3.15.0:
version "3.15.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4"
Expand Down Expand Up @@ -3125,3 +3187,8 @@ yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==

zod@^3.23.8:
version "3.23.8"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d"
integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==

0 comments on commit f0dd33a

Please sign in to comment.