From aec8e9579c118e7e76be3f230a032691aad1341c Mon Sep 17 00:00:00 2001 From: GuY8528 <125722953+GuY8528@users.noreply.github.com> Date: Tue, 18 Feb 2025 16:58:57 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=E3=82=A2=E3=83=8B=E3=83=A1=E3=83=BC?= =?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=B3=E6=94=B9=E5=96=84=EF=BC=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/app/home/page.tsx | 67 +++++++++++++++++++++++--------- web/components/DraggableCard.tsx | 31 ++++++--------- 2 files changed, 60 insertions(+), 38 deletions(-) diff --git a/web/app/home/page.tsx b/web/app/home/page.tsx index aed25364..57062893 100644 --- a/web/app/home/page.tsx +++ b/web/app/home/page.tsx @@ -2,7 +2,7 @@ import type { UserWithCoursesAndSubjects } from "common/types"; import { motion, useAnimation } from "framer-motion"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useLayoutEffect, useRef, useState } from "react"; import { MdClose, MdThumbUp } from "react-icons/md"; import request from "~/api/request"; import { useAboutMe, useRecommended } from "~/api/user"; @@ -18,8 +18,8 @@ export default function Home() { const controls = useAnimation(); const backCardControls = useAnimation(); const [clickedButton, setClickedButton] = useState(""); - const [openDetailedMenu, setOpenDetailedMenu] = useState(false); + const { state: { data: currentUser }, } = useAboutMe(); @@ -29,7 +29,30 @@ export default function Home() { Queue >(() => new Queue([])); - useEffect(() => { + // コンテナと topCard の DOM 参照を用意 + const containerRef = useRef(null); + const topCardRef = useRef(null); + // topCard のコンテナ内での相対位置(backCard の最終的な配置位置)を保存 + const [targetPos, setTargetPos] = useState({ x: 0, y: 0 }); + + // 初期オフセット:右方向へのずれを防ぐため x は 0、縦方向は必要に応じて設定(例: 20) + const initialOffset = { x: 0, y: 0 }; + + // レイアウト完了後に topCard の位置を計算する + useLayoutEffect(() => { + if (topCardRef.current && containerRef.current) { + const containerRect = containerRef.current.getBoundingClientRect(); + const topCardRect = topCardRef.current.getBoundingClientRect(); + setTargetPos({ + x: topCardRect.left - containerRect.left, + y: topCardRect.top - containerRect.top, + }); + // backCard のコントロールに初期オフセットを設定(レンダリング位置と合わせる) + backCardControls.set(initialOffset); + } + }, [backCardControls]); + + useLayoutEffect(() => { if (data) setRecommended(new Queue(data)); }, [data]); @@ -43,23 +66,24 @@ export default function Home() { setClickedButton(action === "accept" ? "heart" : "cross"); - // アニメーション開始前に BackCard の位置をリセット - backCardControls.set({ x: 0, y: 0 }); + // アニメーション開始前に backCard を初期レンダリング位置(initialOffset)に設定 + backCardControls.set(initialOffset); - // 移動アニメーションを実行 await Promise.all([ + // トップカードは画面外へ移動(画面サイズに合わせる) controls.start({ - x: action === "accept" ? 1000 : -1000, + x: action === "accept" ? window.innerWidth : -window.innerWidth, transition: { duration: 0.5, delay: 0.2 }, }), + // backCard は computed した topCard の位置 (targetPos) に移動 backCardControls.start({ - x: 10, - y: 10, + x: targetPos.x, + y: targetPos.y, transition: { duration: 0.5, delay: 0.2 }, }), ]); - // 状態更新 + // キューの更新などの処理 recommended.pop(); if (action === "accept") { await request.send(current.id); @@ -68,13 +92,12 @@ export default function Home() { } rerender({}); - // 位置をリセット + // アニメーション後に位置をリセット(backCard は再び初期レンダリング位置に戻す) controls.set({ x: 0 }); - backCardControls.set({ x: 0, y: 0 }); - + backCardControls.set(initialOffset); setClickedButton(""); }, - [recommended, controls, backCardControls], + [recommended, controls, backCardControls, targetPos], ); if (recommended == null) { @@ -89,20 +112,26 @@ export default function Home() { if (error) throw error; return ( -
+
{displayedUser && (
{nextUser && (
+ {/* backCard: 初期レンダリング位置とアニメーション開始位置を両方とも initialOffset に合わせる */} + {/* トップカード: この位置を基準にするために ref を設定 */} @@ -158,6 +188,7 @@ export default function Home() { ); } +// Queue クラス(状態管理用) class Queue { private store: T[]; constructor(initial: T[]) { @@ -166,7 +197,7 @@ class Queue { push(top: T): void { this.store.push(top); } - // peek(0) to peek the next elem to be popped, peek(1) peeks the second next element to be popped. + // peek(0): 次にポップされる要素、peek(1): その次の要素 peek(nth: number): T | undefined { return this.store[nth]; } diff --git a/web/components/DraggableCard.tsx b/web/components/DraggableCard.tsx index 4856f86e..59d6429e 100644 --- a/web/components/DraggableCard.tsx +++ b/web/components/DraggableCard.tsx @@ -4,7 +4,7 @@ import { useCallback, useState } from "react"; import { MdClose, MdThumbUp } from "react-icons/md"; import { Card } from "./Card"; -const SWIPE_THRESHOLD = 30; +const SWIPE_THRESHOLD = 50; interface DraggableCardProps { displayedUser: UserWithCoursesAndSubjects; @@ -30,22 +30,13 @@ export const DraggableCard = ({ useMotionValueEvent(dragX, "change", (latest: number) => { if (dragging) { - dragX.set(latest); setDragProgress(latest); } else { - dragX.set(0); setDragProgress(0); } }); - useMotionValueEvent(dragY, "change", (latest: number) => { - if (dragging) { - dragY.set(latest); - } else { - dragY.set(0); - } - }); - + // ドラッグ処理の他の部分はそのまま const CardOverlay = () => { return (
@@ -68,14 +59,15 @@ export const DraggableCard = ({ ); }; - const handleDragEnd = useCallback(() => { - const x = dragX.get(); - if (x > SWIPE_THRESHOLD) { - onSwipeRight(); - } - if (x < -SWIPE_THRESHOLD) { - onSwipeLeft(); + const handleDragEnd = useCallback(async () => { + const xValue = dragX.get(); + if (xValue > SWIPE_THRESHOLD) { + await Promise.resolve(onSwipeRight()); + } else if (xValue < -SWIPE_THRESHOLD) { + await Promise.resolve(onSwipeLeft()); } + dragX.stop(); + dragY.stop(); dragX.set(0); dragY.set(0); }, [dragX, dragY, onSwipeRight, onSwipeLeft]); @@ -89,11 +81,10 @@ export const DraggableCard = ({ drag dragElastic={0.9} dragListener={true} - dragConstraints={{ left: 0, right: 0 }} onDragStart={() => setDragging(true)} onDragEnd={() => { - setDragging(false); handleDragEnd(); + setDragging(false); }} style={{ x: dragX, y: dragY, padding: "10px" }} whileTap={{ scale: 0.95 }} From 40a2be9de2df322b73fb167e8c5154e8c7fdf52c Mon Sep 17 00:00:00 2001 From: GuY8528 <125722953+GuY8528@users.noreply.github.com> Date: Thu, 6 Mar 2025 13:51:11 +0900 Subject: [PATCH 2/2] fix --- web/app/home/page.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/web/app/home/page.tsx b/web/app/home/page.tsx index 77de071e..da45ff4b 100644 --- a/web/app/home/page.tsx +++ b/web/app/home/page.tsx @@ -2,7 +2,13 @@ import type { UserWithCoursesAndSubjects } from "common/types"; import { motion, useAnimation } from "framer-motion"; -import { useCallback, useLayoutEffect, useRef, useState } from "react"; +import { + useCallback, + useEffect, + useLayoutEffect, + useRef, + useState, +} from "react"; import { MdClose, MdThumbUp } from "react-icons/md"; import request from "~/api/request"; import { useAboutMe, useRecommended } from "~/api/user"; @@ -57,6 +63,13 @@ export default function Home() { if (data) setRecommended(new Queue(data)); }, [data]); + useEffect(() => { + if (data) { + setRecommended(new Queue(data)); + setLoading(false); + } + }, [data]); + const displayedUser = recommended.peek(0); const nextUser = recommended.peek(1);