feat: mobile menu video auto-play on scroll pause (2s)
- On mobile (<768px): posters shown while scrolling the menu. - After user stops scrolling and lingers on a video item for ~2s: the animation (video) plays in place of the poster (replaces img opacity, plays video, hides gradient). - On scroll/touchmove resume: immediately pause videos and show posters. - Initial load on mobile: auto-plays the first focused video item after settle. - Added isMobile state + resize listener, scroll/touch listeners with 2s debounce timeout. - Helper functions: playMobileVideo(id), pauseAllMobileVideos(), findFocusedVideoItem() (visibility + center scoring). - Data attributes data-id and data-has-video added to .menu-card for reliable targeting. - Desktop hover logic untouched. - Only affects items with .video in menu-data (e.g. Lahore Chana now works on mobile too). - Cleanup on unmount/resize to desktop. - Builds cleanly.
This commit is contained in:
@@ -20,6 +20,7 @@ export default function MenuPage() {
|
||||
const [activeCategory, setActiveCategory] = useState("All");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [showVegetarianOnly, setShowVegetarianOnly] = useState(false);
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
|
||||
const { addToCart } = useCart();
|
||||
const { language } = useLanguage();
|
||||
@@ -67,6 +68,106 @@ export default function MenuPage() {
|
||||
window.scrollTo({ top: 220, behavior: "smooth" });
|
||||
};
|
||||
|
||||
// Mobile video auto-play on scroll pause (2s)
|
||||
useEffect(() => {
|
||||
const updateMobile = () => setIsMobile(window.innerWidth < 768);
|
||||
updateMobile();
|
||||
window.addEventListener("resize", updateMobile);
|
||||
return () => window.removeEventListener("resize", updateMobile);
|
||||
}, []);
|
||||
|
||||
const playMobileVideo = (id: string) => {
|
||||
document.querySelectorAll<HTMLElement>(".menu-card").forEach((card) => {
|
||||
if (card.dataset.id !== id) return;
|
||||
const media = card.querySelector<HTMLElement>(".relative.h-52");
|
||||
if (!media) return;
|
||||
const img = media.querySelector<HTMLImageElement>("img");
|
||||
const video = media.querySelector<HTMLVideoElement>("video");
|
||||
const gradient = media.querySelector<HTMLElement>(".absolute.inset-0.bg-gradient-to-b");
|
||||
if (img) img.style.opacity = "0";
|
||||
if (video) {
|
||||
video.style.opacity = "1";
|
||||
video.play().catch(() => {});
|
||||
}
|
||||
if (gradient) gradient.style.opacity = "0";
|
||||
});
|
||||
};
|
||||
|
||||
const pauseAllMobileVideos = () => {
|
||||
document.querySelectorAll<HTMLElement>(".menu-card").forEach((card) => {
|
||||
const media = card.querySelector<HTMLElement>(".relative.h-52");
|
||||
if (!media) return;
|
||||
const img = media.querySelector<HTMLImageElement>("img");
|
||||
const video = media.querySelector<HTMLVideoElement>("video");
|
||||
const gradient = media.querySelector<HTMLElement>(".absolute.inset-0.bg-gradient-to-b");
|
||||
if (img) img.style.opacity = "";
|
||||
if (video) {
|
||||
video.style.opacity = "0";
|
||||
video.pause();
|
||||
video.currentTime = 0;
|
||||
}
|
||||
if (gradient) gradient.style.opacity = "";
|
||||
});
|
||||
};
|
||||
|
||||
const findFocusedVideoItem = (): string | null => {
|
||||
const cards = document.querySelectorAll<HTMLElement>('.menu-card[data-has-video="true"]');
|
||||
let bestId: string | null = null;
|
||||
let bestScore = 0;
|
||||
const vh = window.innerHeight;
|
||||
const viewportCenter = vh / 2;
|
||||
cards.forEach((card) => {
|
||||
const rect = card.getBoundingClientRect();
|
||||
if (rect.bottom <= 0 || rect.top >= vh) return;
|
||||
const visibleTop = Math.max(rect.top, 0);
|
||||
const visibleBottom = Math.min(rect.bottom, vh);
|
||||
const visibleHeight = visibleBottom - visibleTop;
|
||||
const intersectionRatio = visibleHeight / rect.height;
|
||||
const cardCenter = rect.top + rect.height / 2;
|
||||
const distFromCenter = Math.abs(cardCenter - viewportCenter);
|
||||
const centerScore = Math.max(0, 1 - distFromCenter / (vh / 2));
|
||||
const score = intersectionRatio * 0.6 + centerScore * 0.4;
|
||||
if (score > bestScore && score > 0.15) {
|
||||
bestScore = score;
|
||||
bestId = card.dataset.id || null;
|
||||
}
|
||||
});
|
||||
return bestId;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isMobile) {
|
||||
pauseAllMobileVideos();
|
||||
return;
|
||||
}
|
||||
let scrollTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
const onScrollOrTouch = () => {
|
||||
pauseAllMobileVideos();
|
||||
if (scrollTimeout) clearTimeout(scrollTimeout);
|
||||
scrollTimeout = setTimeout(() => {
|
||||
const focusedId = findFocusedVideoItem();
|
||||
if (focusedId) {
|
||||
playMobileVideo(focusedId);
|
||||
}
|
||||
}, 2000);
|
||||
};
|
||||
window.addEventListener("scroll", onScrollOrTouch, { passive: true });
|
||||
window.addEventListener("touchmove", onScrollOrTouch, { passive: true });
|
||||
// Initial play after load on mobile (if not scrolling)
|
||||
const initialTimeout = setTimeout(() => {
|
||||
const firstFocused = findFocusedVideoItem();
|
||||
if (firstFocused) {
|
||||
playMobileVideo(firstFocused);
|
||||
}
|
||||
}, 1500);
|
||||
return () => {
|
||||
window.removeEventListener("scroll", onScrollOrTouch);
|
||||
window.removeEventListener("touchmove", onScrollOrTouch);
|
||||
if (scrollTimeout) clearTimeout(scrollTimeout);
|
||||
clearTimeout(initialTimeout);
|
||||
};
|
||||
}, [isMobile]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#F8F5F0] text-[#2C2A26]">
|
||||
<Navbar />
|
||||
@@ -184,6 +285,8 @@ export default function MenuPage() {
|
||||
{category.items.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
data-id={item.id}
|
||||
data-has-video={!!item.video}
|
||||
className="menu-card group bg-white border border-[#EDE6D9] rounded-2xl overflow-hidden flex flex-col hover:border-[#c99a2e]/40 active:border-[#c99a2e] active:scale-[0.985] transition-all duration-150 cursor-pointer touch-manipulation"
|
||||
onClick={() => addToCart({ id: item.id, name: item.name, price: item.price, image: item.image })}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user