-
Notifications
You must be signed in to change notification settings - Fork 1
feat: 조회수 페이지 UI #97
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: 조회수 페이지 UI #97
Changes from all commits
c45b812
f7f0323
820e0de
3d5cdf6
0cff79f
5c6d3fc
1f5669d
857b87c
731ea72
2c47d36
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,8 @@ | ||
| import Button from "@/components/common/Button"; | ||
| import Lottie from "lottie-react"; | ||
| import gsap from "gsap"; | ||
| import { ScrollTrigger } from "gsap/ScrollTrigger"; | ||
| import { useGSAP } from "@gsap/react"; | ||
| import { BREAK_POINTS, DESIGN_SYSTEM_COLOR } from "@/style/variable"; | ||
| import { css } from "@emotion/react"; | ||
| import { | ||
|
|
@@ -21,13 +24,15 @@ import MAINBOTTOMIMG from "@/assets/main-bottom.gif"; | |
| import NewletterAni from "@/assets/newletter.json"; | ||
| import CommunityAni from "@/assets/community.json"; | ||
| import { useGetChartDataQuery } from "@/api/chartApi"; | ||
| import { useEffect, useState } from "react"; | ||
| import { useEffect, useRef, useState } from "react"; | ||
| import { useNavigate } from "react-router-dom"; | ||
| import { useModal } from "@/hooks/useModal.ts"; | ||
| import { useAtom } from "jotai"; | ||
| import { loginState } from "@/store/user"; | ||
| import { SubscriptionModalContent } from "@/components/common/modal/SubscriptionModalContent"; | ||
| import FloatingWidget from "@/components/app/home/floating/floatingWidget"; | ||
| import { useApiTotalView } from "@/hooks/api/visitor/useApiTotalView"; | ||
| import { VisitorCount } from "@/components/app/home/VisitorCount"; | ||
|
|
||
| type CoinInfoType = { | ||
| [coin in "BTC" | "ETH" | "XRP"]: { | ||
|
|
@@ -37,6 +42,8 @@ type CoinInfoType = { | |
| }; | ||
| }; | ||
|
|
||
| gsap.registerPlugin(ScrollTrigger); | ||
|
|
||
| export default function HomePage() { | ||
| // 차트 데이터 가지고 오기 | ||
| const getBTCData = useGetChartDataQuery("BTC"); | ||
|
|
@@ -48,6 +55,77 @@ export default function HomePage() { | |
| const { open, close } = useModal(); | ||
| const navigate = useNavigate(); | ||
|
|
||
| const { data: totalView, refetch, sendTotalView } = useApiTotalView(); | ||
| const topNumberRef = useRef<HTMLSpanElement>(null); | ||
| const bottomNumberRef = useRef<HTMLSpanElement>(null); | ||
| const topSectionRef = useRef<HTMLDivElement>(null); | ||
| const bottomSectionRef = useRef<HTMLDivElement>(null); | ||
| const [currentNum, setCurrentNum] = useState<number|string>(0); | ||
| const [animationTotal, setAnimationTotal] = useState(0); | ||
|
|
||
| const slotAnimation = async () => { | ||
| if (!topNumberRef.current || !bottomNumberRef.current) return; | ||
|
|
||
| await refetch(); | ||
|
|
||
| setCurrentNum(0); | ||
|
|
||
| let current = 0; | ||
| const step = Math.ceil(animationTotal / 20000); | ||
|
|
||
| const animate = () => { | ||
| current += step; | ||
| if (current >= animationTotal) { | ||
| setCurrentNum(animationTotal); | ||
| setCurrentNum(animationTotal.toLocaleString()); | ||
| return; | ||
| } | ||
|
|
||
| setCurrentNum(current.toLocaleString()); | ||
| requestAnimationFrame(animate); | ||
| }; | ||
|
|
||
| requestAnimationFrame(animate); | ||
| }; | ||
|
Comment on lines
+66
to
+89
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Optimize animation and API calling strategy. The current implementation has several issues:
const slotAnimation = async () => {
+ // Prevent multiple animations from running simultaneously
+ if (isAnimating.current) return;
if (!topNumberRef.current || !bottomNumberRef.current) return;
+ isAnimating.current = true;
- await refetch();
+ // Only refetch if needed (could add a time-based check)
+ // await refetch();
setCurrentNum(0);
let current = 0;
const step = Math.ceil(animationTotal / 20000);
const animate = () => {
current += step;
if (current >= animationTotal) {
- setCurrentNum(animationTotal);
setCurrentNum(animationTotal.toLocaleString());
+ isAnimating.current = false;
return;
}
setCurrentNum(current.toLocaleString());
requestAnimationFrame(animate);
};
requestAnimationFrame(animate);
};To implement this, add this ref at the top of your component: const isAnimating = useRef(false); |
||
|
|
||
| useGSAP(() => { | ||
| if (!topSectionRef.current || !bottomSectionRef.current) { | ||
| return; | ||
| } | ||
|
|
||
| const ctx = gsap.context(() => { | ||
| ScrollTrigger.create({ | ||
| trigger: topSectionRef.current, | ||
| start: "top 5%", | ||
| end: "top 5%", | ||
| toggleActions: "restart complete restart reset", | ||
| onEnter: () => slotAnimation(), | ||
| onEnterBack: () => slotAnimation(), | ||
| }); | ||
|
|
||
| ScrollTrigger.create({ | ||
| trigger: bottomSectionRef.current, | ||
| start: "bottom 80%", | ||
| toggleActions: "restart complete restart reset", | ||
| onEnter: () => slotAnimation(), | ||
| onEnterBack: () => slotAnimation(), | ||
| }); | ||
|
|
||
| slotAnimation(); | ||
| }); | ||
|
|
||
| return () => ctx.revert(); | ||
| }, { dependencies: [animationTotal] }); | ||
|
|
||
| useEffect(() => { | ||
| sendTotalView(); | ||
| }, []); | ||
|
Comment on lines
+120
to
+122
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainAPI call should have appropriate dependency. The If 🏁 Script executed: #!/bin/bash
# Check how sendTotalView is implemented in the hook
rg -A 5 "useApiTotalView" --type tsLength of output: 1334 🏁 Script executed: #!/bin/bash
sed -n '1,200p' src/hooks/api/visitor/useApiTotalView.tsLength of output: 678 Stabilize The • Memoize
• If you truly intend a “run once on mount” side‑effect, explicitly disable the lint rule with a comment: useEffect(() => {
sendTotalView();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);Please pick one approach to satisfy exhaustive‑deps and document your intent. Locations to update:
|
||
|
|
||
| useEffect(() => { | ||
| if (!totalView) return; | ||
| setAnimationTotal(totalView.data); | ||
| }, [totalView]); | ||
|
|
||
| useEffect(() => { | ||
| if (!getBTCData.isSuccess || !getETHData.isSuccess || !getXRPData.isSuccess) | ||
| return; | ||
|
|
@@ -161,10 +239,16 @@ export default function HomePage() { | |
| > | ||
| {/* LEFT SIDE */} | ||
| <div> | ||
| <VisitorCount | ||
| currentNum={currentNum} | ||
| numberRef={topNumberRef} | ||
| sectionRef={topSectionRef} | ||
| /> | ||
| <h1 | ||
| css={css` | ||
| font-size: 4.8rem; | ||
| line-height: 7.2rem; | ||
| margin-top: 20px; | ||
|
|
||
| ${BREAK_POINTS.TABLET} { | ||
| } | ||
|
|
@@ -529,6 +613,15 @@ export default function HomePage() { | |
| alt="" | ||
| /> | ||
| </section> | ||
| {/* 하단 배너 */} | ||
| <section> | ||
| <VisitorCount | ||
| currentNum={currentNum} | ||
| numberRef={bottomNumberRef} | ||
| sectionRef={bottomSectionRef} | ||
| isBottom | ||
| /> | ||
| </section> | ||
| </article> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gsap이라는 라이브러리를 처음봐서 검색을 조금 해보았는데요!https://www.npmjs.com/package/@gsap/react
이런 라이브러리가 있는데,
gsap그대로 사용을 하신 이유가 있을까요?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gsap 정보가 많아서 그걸로 개발했었는데, 댓글 보고 @gsap/react를 알게 됐습니다!
찾아보고 써보니까 React에서 GSAP을 좀 더 깔끔하게 쓸 수 있더라구요
@gsap/react로 수정해서 올리겠습니다~!