22
33import type { UserWithCoursesAndSubjects } from "common/types" ;
44import { motion , useAnimation } from "framer-motion" ;
5- import { useCallback , useEffect , useState } from "react" ;
5+ import {
6+ useCallback ,
7+ useEffect ,
8+ useLayoutEffect ,
9+ useRef ,
10+ useState ,
11+ } from "react" ;
612import { MdClose , MdThumbUp } from "react-icons/md" ;
713import request from "~/api/request" ;
814import { useAboutMe , useRecommended } from "~/api/user" ;
@@ -18,8 +24,8 @@ export default function Home() {
1824 const controls = useAnimation ( ) ;
1925 const backCardControls = useAnimation ( ) ;
2026 const [ clickedButton , setClickedButton ] = useState < string > ( "" ) ;
21-
2227 const [ openDetailedMenu , setOpenDetailedMenu ] = useState ( false ) ;
28+
2329 const {
2430 state : { data : currentUser } ,
2531 } = useAboutMe ( ) ;
@@ -30,6 +36,33 @@ export default function Home() {
3036 > ( ( ) => new Queue ( [ ] ) ) ;
3137 const [ loading , setLoading ] = useState < boolean > ( true ) ;
3238
39+ // コンテナと topCard の DOM 参照を用意
40+ const containerRef = useRef < HTMLDivElement > ( null ) ;
41+ const topCardRef = useRef < HTMLDivElement > ( null ) ;
42+ // topCard のコンテナ内での相対位置(backCard の最終的な配置位置)を保存
43+ const [ targetPos , setTargetPos ] = useState ( { x : 0 , y : 0 } ) ;
44+
45+ // 初期オフセット:右方向へのずれを防ぐため x は 0、縦方向は必要に応じて設定(例: 20)
46+ const initialOffset = { x : 0 , y : 0 } ;
47+
48+ // レイアウト完了後に topCard の位置を計算する
49+ useLayoutEffect ( ( ) => {
50+ if ( topCardRef . current && containerRef . current ) {
51+ const containerRect = containerRef . current . getBoundingClientRect ( ) ;
52+ const topCardRect = topCardRef . current . getBoundingClientRect ( ) ;
53+ setTargetPos ( {
54+ x : topCardRect . left - containerRect . left ,
55+ y : topCardRect . top - containerRect . top ,
56+ } ) ;
57+ // backCard のコントロールに初期オフセットを設定(レンダリング位置と合わせる)
58+ backCardControls . set ( initialOffset ) ;
59+ }
60+ } , [ backCardControls ] ) ;
61+
62+ useLayoutEffect ( ( ) => {
63+ if ( data ) setRecommended ( new Queue ( data ) ) ;
64+ } , [ data ] ) ;
65+
3366 useEffect ( ( ) => {
3467 if ( data ) {
3568 setRecommended ( new Queue ( data ) ) ;
@@ -47,23 +80,24 @@ export default function Home() {
4780
4881 setClickedButton ( action === "accept" ? "heart" : "cross" ) ;
4982
50- // アニメーション開始前に BackCard の位置をリセット
51- backCardControls . set ( { x : 0 , y : 0 } ) ;
83+ // アニメーション開始前に backCard を初期レンダリング位置(initialOffset)に設定
84+ backCardControls . set ( initialOffset ) ;
5285
53- // 移動アニメーションを実行
5486 await Promise . all ( [
87+ // トップカードは画面外へ移動(画面サイズに合わせる)
5588 controls . start ( {
56- x : action === "accept" ? 1000 : - 1000 ,
89+ x : action === "accept" ? window . innerWidth : - window . innerWidth ,
5790 transition : { duration : 0.5 , delay : 0.2 } ,
5891 } ) ,
92+ // backCard は computed した topCard の位置 (targetPos) に移動
5993 backCardControls . start ( {
60- x : 10 ,
61- y : 10 ,
94+ x : targetPos . x ,
95+ y : targetPos . y ,
6296 transition : { duration : 0.5 , delay : 0.2 } ,
6397 } ) ,
6498 ] ) ;
6599
66- // 状態更新
100+ // キューの更新などの処理
67101 recommended . pop ( ) ;
68102 if ( action === "accept" ) {
69103 await request . send ( current . id ) ;
@@ -72,13 +106,12 @@ export default function Home() {
72106 }
73107 rerender ( { } ) ;
74108
75- // 位置をリセット
109+ // アニメーション後に位置をリセット(backCard は再び初期レンダリング位置に戻す)
76110 controls . set ( { x : 0 } ) ;
77- backCardControls . set ( { x : 0 , y : 0 } ) ;
78-
111+ backCardControls . set ( initialOffset ) ;
79112 setClickedButton ( "" ) ;
80113 } ,
81- [ recommended , controls , backCardControls ] ,
114+ [ recommended , controls , backCardControls , targetPos ] ,
82115 ) ;
83116
84117 if ( loading ) {
@@ -93,20 +126,26 @@ export default function Home() {
93126 if ( error ) throw error ;
94127
95128 return (
96- < div className = "flex h-full flex-col items-center justify-center p-4" >
129+ < div
130+ ref = { containerRef }
131+ className = "flex h-full flex-col items-center justify-center p-4"
132+ >
97133 { displayedUser && (
98134 < div className = "flex h-full flex-col items-center justify-center" >
99135 { nextUser && (
100136 < div className = "relative grid h-full w-full grid-cols-1 grid-rows-1" >
137+ { /* backCard: 初期レンダリング位置とアニメーション開始位置を両方とも initialOffset に合わせる */ }
101138 < motion . div
102139 className = "z-0 col-start-1 row-start-1 mt-4"
103- initial = { { x : 0 , y : 0 } } // 初期位置を (0, 0) に設定
140+ initial = { initialOffset }
104141 animate = { backCardControls }
105142 >
106143 < Card displayedUser = { nextUser } currentUser = { currentUser } />
107144 </ motion . div >
145+ { /* トップカード: この位置を基準にするために ref を設定 */ }
108146 < motion . div
109- className = "z-10 col-start-1 row-start-1 mt-4 flex items-center justify-center"
147+ ref = { topCardRef }
148+ className = "z-10 col-start-1 row-start-1 mt-4"
110149 animate = { controls }
111150 >
112151 < DraggableCard
@@ -123,6 +162,7 @@ export default function Home() {
123162 { nextUser == null && (
124163 < div className = "relative grid h-full w-full grid-cols-1 grid-rows-1" >
125164 < motion . div
165+ ref = { topCardRef }
126166 className = "z-10 col-start-1 row-start-1 mt-4 flex items-center justify-center"
127167 animate = { controls }
128168 >
@@ -162,6 +202,7 @@ export default function Home() {
162202 ) ;
163203}
164204
205+ // Queue クラス(状態管理用)
165206class Queue < T > {
166207 private store : T [ ] ;
167208 constructor ( initial : T [ ] ) {
@@ -170,7 +211,7 @@ class Queue<T> {
170211 push ( top : T ) : void {
171212 this . store . push ( top ) ;
172213 }
173- // peek(0) to peek the next elem to be popped, peek (1) peeks the second next element to be popped.
214+ // peek(0): 次にポップされる要素、 peek(1): その次の要素
174215 peek ( nth : number ) : T | undefined {
175216 return this . store [ nth ] ;
176217 }
0 commit comments