Skip to content
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] 라운드 통계 화면 및 게임 결과 화면 구현 #73

Merged
merged 28 commits into from
Jul 25, 2024
Merged
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e61ea09
chore: 동적 파라미터 라우팅 설정 #50
rbgksqkr Jul 23, 2024
0128c04
refactor: BalanceContent 인터페이스 수정 #50
rbgksqkr Jul 23, 2024
752c497
feat: 라우팅 경로 수정 및 라운드 통계 분기처리 #50
rbgksqkr Jul 23, 2024
9650f21
feat: 라운드 통계 수정된 UI 반영 및 Tab UI 구현 #50
rbgksqkr Jul 23, 2024
91f40b3
refactor: 사용하지 않는 파일 삭제 #50
rbgksqkr Jul 23, 2024
c65abe3
chore: stylelint css 속성 추가 #50
rbgksqkr Jul 23, 2024
aa3b05a
feat: 숫자 카운팅 애니메이션 커스텀 훅 구현 #50
rbgksqkr Jul 24, 2024
a200136
feat: roundVoteResult 인터페이스 선언 및 msw 설정 #50
rbgksqkr Jul 24, 2024
be3f49a
refactor: 그룹 통계와 전체 통계 서버 상태를 react-query로 관리 #50
rbgksqkr Jul 24, 2024
7103261
refactor: 그룹 통계와 전체 통계 컴포넌트 분리 #50
rbgksqkr Jul 24, 2024
2d26903
refactor: Header 홈 아이콘 대신 현재 라운드 표시 #50
rbgksqkr Jul 24, 2024
a04c200
fix: Button disabled 설정 추가 #50
rbgksqkr Jul 24, 2024
d8a6d53
feat: 다음 라운드로 이동하는 API 통신 구현 #50
rbgksqkr Jul 24, 2024
f46054d
feat: 최종 게임 결과 불러오는 API 구현 #50
rbgksqkr Jul 24, 2024
090e2e4
feat: Header 분기처리 및 라운드 API 불러오기 #50
rbgksqkr Jul 24, 2024
27715e0
feat: 최종 결과 페이지 UI 퍼블리싱 및 API 연결 #50
rbgksqkr Jul 24, 2024
73c9f90
refactor: GameResultItem 컴포넌트 분리 #50
rbgksqkr Jul 24, 2024
93dd3c3
refactor: placeholderData를 이용하여 초기값을 설정하고, 라운드 결과 애니메이션 적용 #50
rbgksqkr Jul 24, 2024
76a259c
chore: console error eslint 룰 추가 #50
rbgksqkr Jul 25, 2024
6e4ec20
refactor: 다음 라운드로 넘어가는 버튼 컴포넌트 분리 #50
rbgksqkr Jul 25, 2024
0a65739
refactor: Tab 컴포넌트 분리 #50
rbgksqkr Jul 25, 2024
261aa4e
refactor: TabContentContainer 컴포넌트 분리 #50
rbgksqkr Jul 25, 2024
347cfb5
refactor: 게임 결과 페이지의 FinalButton 컴포넌트 분리 #50
rbgksqkr Jul 25, 2024
adbed42
design: 헤더 아이콘 크기 고정 및 버튼 패딩 제거 #50
rbgksqkr Jul 25, 2024
dfc98e4
refactor: 라우팅 경로 상수화 및 헤더 컴포넌트 분기 처리 #50
rbgksqkr Jul 25, 2024
7a2deb5
fix: 컴포넌트명과 타입명이 같아서 생기는 빌드 오류 해결 #50
rbgksqkr Jul 25, 2024
8defa35
style: 카운팅 애니메이션 주석 추가 #50
rbgksqkr Jul 25, 2024
04ffc25
refactor: 라운드 통계 화면 탭과 TabItem 컴포넌트명 수정 #50
rbgksqkr Jul 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -76,7 +76,7 @@ export const barStyle = (percentage: number, isBigFirstOption: boolean) => css`
font-weight: bold;
font-size: 1.6rem;
clip-path: polygon(0 0, 100% 0, calc(100% - 10px) 100%, 0 100%);
transition: all 1s;
transition: all 2s;
transform: translateX(5px);
`;

@@ -94,7 +94,7 @@ export const barBackgroundStyle = (percentage: number, isBigFirstOption: boolean
font-weight: bold;
font-size: 1.6rem;
clip-path: polygon(10px 0, 100% 0, 100% 100%, 0 100%);
transition: all 1s;
transition: all 2s;
transform: translateX(-5px);
`;

11 changes: 9 additions & 2 deletions frontend/src/components/RoundResultTab/RoundResultTab.tsx
Original file line number Diff line number Diff line change
@@ -16,6 +16,8 @@ import {
tabWrapperStyle,
} from './RoundResultTab.styled';

import useCountAnimation from '@/hooks/useCountAnimation';

interface Option {
percentage: number;
count: number;
@@ -42,6 +44,11 @@ const RoundResultTab = () => {
const [groupResult, setGroupResult] = useState<Statistic>(INITIAL_STATISTIC);
const [averageResult, setAverageResult] = useState<Statistic>(INITIAL_STATISTIC);

const animatedFirstPercentage = useCountAnimation({ target: groupResult.firstOption.percentage });
const animatedSecondPercentage = useCountAnimation({
target: groupResult.secondOption.percentage,
});

const isBigFirstOption = groupResult.firstOption.percentage >= 50;

useEffect(() => {
@@ -95,10 +102,10 @@ const RoundResultTab = () => {
</div>
<div css={barWrapperStyle}>
<div css={barStyle(groupResult.firstOption.percentage, isBigFirstOption)}>
{groupResult.firstOption.percentage}%
{animatedFirstPercentage}%
</div>
<div css={barBackgroundStyle(groupResult.secondOption.percentage, isBigFirstOption)}>
{groupResult.secondOption.percentage}%
{animatedSecondPercentage}%
</div>
</div>
<div css={resultTextStyle}>
35 changes: 35 additions & 0 deletions frontend/src/hooks/useCountAnimation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useEffect, useState } from 'react';

// "ease out" : 빨라졌다가 천천히 끝나는 애니메이션
const easeOutRate = (timingRate: number) => {
return timingRate === 1 ? 1 : 1 - Math.pow(2, -10 * timingRate);
};

interface UseCountAnimationProps {
target: number;
start?: number;
duration?: number;
}

const useCountAnimation = ({ target, start = 50, duration = 2000 }: UseCountAnimationProps) => {
const [count, setCount] = useState(start);
const frameRate = 500 / 60;
const totalFrame = Math.round(duration / frameRate);

useEffect(() => {
if (target === start) return; // target 값을 API로 불러올 경우 초기값이 애니메이션에 반영되므로 예외처리
let currentNumber = start;
const counter = setInterval(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

L5 - 참고 의견

setInterval이 외에도 웹 애니메이션을 최적화하는 requestAnimationFrame 이라는 것도 있더라고요? 저도 이번에 리뷰하면서 알게되었답니다 ㅎ .ㅎ 다음에 최적화하면서 적용해 봐도 좋을 것 같아요 ~!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 저도 숫자 올라갈 때마다 렌더링되는게 문제가 될 수 있다고 생각했는데 requestAnimationFrame 을 적용해볼 수 있겠군요! 나중에 시간이나 성능 비교해보면서 한번에 적용해보면 좋을 것 같아요 ㅎㅎ

https://velog.io/@younghwanjoe/requestAnimationFrame%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-%EC%83%81

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

말우 ! 좋은 자료 공유 호라락 읽어보았읍니다 감사룡. . 지금 당장은 이해가 완벽히 안됐지만 역시 해 봐야겠죠 ? 푸하하 최적화 때 야무지게 써 버립시다 ^ .^ ~ ~

const progress = easeOutRate(++currentNumber / totalFrame);
setCount(Math.round(target * progress));

if (progress === 1) {
clearInterval(counter);
}
}, frameRate);
}, [target, frameRate, start, totalFrame]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

L3 - 중요 질문

의존성 배열에 상수로 정의된 frameRate 와 totalFrame도 들어간 이유가 무엇인가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

의존성 배열에는 반응형 값이 들어가야하는데, states, props, 컴포넌트 내에서 선언한 변수 중 useEffect에서 사용되는 데이터들이 추가되어야 합니다! frameRate와 totalFrame은 컴포넌트 내에 선언되었고, useEffect에서 사용되므로 의존성 배열에 추가되는 것이 맞습니다.

그런데 다시 보니 상수가 불필요하게 의존되는 것 같아 useEffect 내부로 옮겨 반응형 값이 안되도록 수정하겠습니다!


return count;
};

export default useCountAnimation;