feat: add count-up animation to stats on scroll#355
Conversation
📝 WalkthroughWalkthroughCTASection replaces static numeric stats displays with animated counters. A new ChangesCTA Stats Count-up Animation
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@client/src/components/CTASection.tsx`:
- Around line 9-25: The animation effect in useEffect incorrectly checks
startTime with a falsy check and never cancels the RAF; change the condition
inside the step function to use startTime === null (or startTime == null) so a 0
timestamp isn't treated as unset, capture the requestAnimationFrame id returned
when calling requestAnimationFrame(step) into a variable (e.g., rafId) and call
cancelAnimationFrame(rafId) in the effect cleanup, and ensure you also guard
against continuing to call requestAnimationFrame after unmount by using that
rafId or a mounted flag; update references to startTime, step,
requestAnimationFrame and setCount accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9efe6a1e-92e3-449f-a2a6-1e8182e607bf
📒 Files selected for processing (1)
client/src/components/CTASection.tsx
| useEffect(() => { | ||
| if (!isInView) return; | ||
|
|
||
| let startTime: number | null = null; | ||
| const startValue = 0; | ||
|
|
||
| const step = (timestamp: number) => { | ||
| if (!startTime) startTime = timestamp; | ||
| const progress = Math.min((timestamp - startTime) / duration, 1); | ||
| // easeOutQuart for smooth deceleration | ||
| const eased = 1 - Math.pow(1 - progress, 4); | ||
| setCount(Math.floor(eased * (target - startValue) + startValue)); | ||
| if (progress < 1) requestAnimationFrame(step); | ||
| }; | ||
|
|
||
| requestAnimationFrame(step); | ||
| }, [isInView, target, duration]); |
There was a problem hiding this comment.
Cancel RAF on cleanup and use a null check for startTime.
On Line 16, if (!startTime) should be startTime === null; a 0 timestamp is falsy.
Also, the effect never cancels the pending requestAnimationFrame, so it can keep running after unmount.
Suggested fix
useEffect(() => {
if (!isInView) return;
let startTime: number | null = null;
+ let frameId: number | null = null;
const startValue = 0;
const step = (timestamp: number) => {
- if (!startTime) startTime = timestamp;
+ if (startTime === null) startTime = timestamp;
const progress = Math.min((timestamp - startTime) / duration, 1);
// easeOutQuart for smooth deceleration
const eased = 1 - Math.pow(1 - progress, 4);
setCount(Math.floor(eased * (target - startValue) + startValue));
- if (progress < 1) requestAnimationFrame(step);
+ if (progress < 1) frameId = requestAnimationFrame(step);
};
- requestAnimationFrame(step);
+ frameId = requestAnimationFrame(step);
+ return () => {
+ if (frameId !== null) cancelAnimationFrame(frameId);
+ };
}, [isInView, target, duration]);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| useEffect(() => { | |
| if (!isInView) return; | |
| let startTime: number | null = null; | |
| const startValue = 0; | |
| const step = (timestamp: number) => { | |
| if (!startTime) startTime = timestamp; | |
| const progress = Math.min((timestamp - startTime) / duration, 1); | |
| // easeOutQuart for smooth deceleration | |
| const eased = 1 - Math.pow(1 - progress, 4); | |
| setCount(Math.floor(eased * (target - startValue) + startValue)); | |
| if (progress < 1) requestAnimationFrame(step); | |
| }; | |
| requestAnimationFrame(step); | |
| }, [isInView, target, duration]); | |
| useEffect(() => { | |
| if (!isInView) return; | |
| let startTime: number | null = null; | |
| let frameId: number | null = null; | |
| const startValue = 0; | |
| const step = (timestamp: number) => { | |
| if (startTime === null) startTime = timestamp; | |
| const progress = Math.min((timestamp - startTime) / duration, 1); | |
| // easeOutQuart for smooth deceleration | |
| const eased = 1 - Math.pow(1 - progress, 4); | |
| setCount(Math.floor(eased * (target - startValue) + startValue)); | |
| if (progress < 1) frameId = requestAnimationFrame(step); | |
| }; | |
| frameId = requestAnimationFrame(step); | |
| return () => { | |
| if (frameId !== null) cancelAnimationFrame(frameId); | |
| }; | |
| }, [isInView, target, duration]); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/src/components/CTASection.tsx` around lines 9 - 25, The animation
effect in useEffect incorrectly checks startTime with a falsy check and never
cancels the RAF; change the condition inside the step function to use startTime
=== null (or startTime == null) so a 0 timestamp isn't treated as unset, capture
the requestAnimationFrame id returned when calling requestAnimationFrame(step)
into a variable (e.g., rafId) and call cancelAnimationFrame(rafId) in the effect
cleanup, and ensure you also guard against continuing to call
requestAnimationFrame after unmount by using that rafId or a mounted flag;
update references to startTime, step, requestAnimationFrame and setCount
accordingly.
Description
Added a smooth count-up animation to the CTA stats section that triggers when the section scrolls into view.
The stats now animate dynamically instead of displaying static values, improving user engagement and making the landing page feel more interactive and polished.
Changes Made
StatItemcomponentuseCountUphookuseInViewFiles Changed
client/src/components/CTASection.tsxIssue
Fixes #354
Testing
Note
The repository currently contains unrelated existing TypeScript/build issues in other files that were not introduced by this PR. The CTA section enhancement itself works correctly and has been tested successfully in local development mode.
Before
After
after.Recording.-.Trim.mp4
Summary by CodeRabbit