From 683aa657e6964e32da9017ff5e58c475ad3fdb13 Mon Sep 17 00:00:00 2001
From: sebastjanartic <45803536-sebastjanartic@users.noreply.replit.com>
Date: Fri, 29 Aug 2025 15:17:08 +0000
Subject: [PATCH] Improve video scrolling to create a continuous, flowing
experience
Refactors the `CategoryRow` component to implement smooth, continuous scrolling using `requestAnimationFrame` and `translateX` for a seamless video-to-video transition, replacing the previous interval-based circular carousel.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 2eb1084e-b728-4449-9231-f1665924c8d5
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/2eb1084e-b728-4449-9231-f1665924c8d5/QCN70f2
---
client/src/components/netflix-grid.tsx | 121 +++++++++++++++----------
1 file changed, 75 insertions(+), 46 deletions(-)
diff --git a/client/src/components/netflix-grid.tsx b/client/src/components/netflix-grid.tsx
index 7ec0f50..4866da5 100644
--- a/client/src/components/netflix-grid.tsx
+++ b/client/src/components/netflix-grid.tsx
@@ -137,35 +137,55 @@ function CategoryRow({ category, onVideoClick }: CategoryRowProps) {
const [showLeftButton, setShowLeftButton] = useState(false);
const [showRightButton, setShowRightButton] = useState(true);
- const [currentIndex, setCurrentIndex] = useState(0);
+ const [translateX, setTranslateX] = useState(0);
+ const [isScrolling, setIsScrolling] = useState(false);
const videosToShow = 5; // Show 5 videos at a time
+ const videoWidth = 120; // Width including spacing
const scroll = (direction: 'left' | 'right') => {
- const totalVideos = category.videos.length;
- if (direction === 'right') {
- // Move to next video in circular fashion: 1→2→3→...→10→1→2→...
- setCurrentIndex(prev => (prev + 1) % totalVideos);
- } else {
- // Move to previous video in circular fashion: 1→10→9→...→2→1→10→...
- setCurrentIndex(prev => prev === 0 ? totalVideos - 1 : prev - 1);
- }
+ const step = direction === 'right' ? -videoWidth : videoWidth;
+ setTranslateX(prev => prev + step);
};
const startAutoScroll = (direction: 'left' | 'right') => {
- // Clear any existing interval
+ // Clear any existing animation
if (scrollIntervalRef.current) {
- clearInterval(scrollIntervalRef.current);
+ cancelAnimationFrame(scrollIntervalRef.current);
}
- // Start continuous circular scrolling
- scrollIntervalRef.current = setInterval(() => {
- scroll(direction);
- }, 600); // Auto scroll every 600ms
+ setIsScrolling(true);
+
+ // Continuous smooth flow - videos flow like a stream
+ const speed = direction === 'right' ? -2 : 2; // Pixels per frame
+
+ const animate = () => {
+ setTranslateX(prev => {
+ const newX = prev + speed;
+ const totalWidth = category.videos.length * videoWidth;
+
+ // Infinite loop - seamless reset
+ if (direction === 'right' && newX <= -totalWidth * 2) {
+ return -totalWidth; // Reset to middle section
+ } else if (direction === 'left' && newX >= 0) {
+ return -totalWidth; // Reset to middle section
+ }
+
+ return newX;
+ });
+
+ if (isScrolling) {
+ scrollIntervalRef.current = requestAnimationFrame(animate);
+ }
+ };
+
+ animate();
};
- // Always start with video 1 visible at first position
+ // Initialize in middle section for infinite scroll
useEffect(() => {
- setCurrentIndex(0); // Start with video 1 at first position
+ if (category.videos.length > 0) {
+ setTranslateX(-category.videos.length * videoWidth); // Start in middle section
+ }
}, [category.videos.length]);
// Always show both buttons
@@ -175,8 +195,9 @@ function CategoryRow({ category, onVideoClick }: CategoryRowProps) {
}, []);
const stopAutoScroll = () => {
+ setIsScrolling(false);
if (scrollIntervalRef.current) {
- clearInterval(scrollIntervalRef.current);
+ cancelAnimationFrame(scrollIntervalRef.current);
scrollIntervalRef.current = null;
}
};
@@ -270,34 +291,42 @@ function CategoryRow({ category, onVideoClick }: CategoryRowProps) {