From 7fd7127003398a269f1f765208c8baced728aafb Mon Sep 17 00:00:00 2001 From: sebastjanartic <45803536-sebastjanartic@users.noreply.replit.com> Date: Fri, 29 Aug 2025 09:40:46 +0000 Subject: [PATCH] Add video preview on hover for a better user browsing experience Implement a hover effect on video cards to show a muted, auto-playing preview of the video after a short delay, using Video.js and HLS.js for adaptive streaming. 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/bdLHpJy --- client/src/components/video-card.tsx | 77 ++++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 10 deletions(-) diff --git a/client/src/components/video-card.tsx b/client/src/components/video-card.tsx index a447925..960f9b5 100644 --- a/client/src/components/video-card.tsx +++ b/client/src/components/video-card.tsx @@ -1,6 +1,7 @@ import { Play } from "lucide-react"; import { type Video } from "@shared/schema"; import HLSPreviewThumbnail from "./hls-preview-thumbnail"; +import { useState, useRef, useEffect } from "react"; interface VideoCardProps { video: Video; @@ -42,20 +43,47 @@ function formatDate(date: Date | string): string { } export default function VideoCard({ video, onClick, className = "" }: VideoCardProps) { + const [isHovered, setIsHovered] = useState(false); + const [showPreview, setShowPreview] = useState(false); + const hoverTimeoutRef = useRef(); + + // Delay preview start to avoid loading on quick mouse passes + useEffect(() => { + if (isHovered) { + hoverTimeoutRef.current = setTimeout(() => { + setShowPreview(true); + }, 800); // Start preview after 800ms hover + } else { + if (hoverTimeoutRef.current) { + clearTimeout(hoverTimeoutRef.current); + } + setShowPreview(false); + } + + return () => { + if (hoverTimeoutRef.current) { + clearTimeout(hoverTimeoutRef.current); + } + }; + }, [isHovered]); + return (
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} > - {/* Simple thumbnail with fallback - no HLS loading until needed */} + {/* Video preview container */}
onClick?.(video)} > + {/* Static thumbnail - always visible */} {video.title} + {/* Video preview - only load when hovering */} + {showPreview && ( +
+ +
+ )} + {/* Duration badge */} -
+
{formatDuration(video.duration)}
- - - {/* Play button overlay */} -
-
-
+ {/* Play button overlay - hidden during preview */} + {!showPreview && ( +
+
+
+
-
+ )}