feat: add pause functionality to explore page banner for improved user interaction

This commit is contained in:
CodingOnStar 2025-10-15 10:36:09 +08:00
parent 53a80a5dbe
commit df76527f29
3 changed files with 16 additions and 10 deletions

View File

@ -22,9 +22,10 @@ export type BannerData = {
type BannerItemProps = {
banner: BannerData
autoplayDelay: number
isPaused?: boolean
}
export const BannerItem: FC<BannerItemProps> = ({ banner, autoplayDelay }) => {
export const BannerItem: FC<BannerItemProps> = ({ banner, autoplayDelay, isPaused = false }) => {
const { t } = useTranslation()
const { api, selectedIndex } = useCarousel()
const [resetKey, setResetKey] = useState(0)
@ -102,6 +103,7 @@ export const BannerItem: FC<BannerItemProps> = ({ banner, autoplayDelay }) => {
isNextSlide={index === slideInfo.nextIndex}
autoplayDelay={autoplayDelay}
resetKey={resetKey}
isPaused={isPaused}
onClick={() => handleIndicatorClick(index)}
/>
))}

View File

@ -1,5 +1,5 @@
import type { FC } from 'react'
import React, { useMemo } from 'react'
import React, { useMemo, useState } from 'react'
import { Carousel } from '@/app/components/base/carousel'
import { useGetBanners } from '@/service/use-explore'
import Loading from '../../base/loading'
@ -12,6 +12,7 @@ const MIN_LOADING_HEIGHT = 168
const Banner: FC = () => {
const { locale } = useI18N()
const { data: banners, isLoading, isError } = useGetBanners(locale)
const [isHovered, setIsHovered] = useState(false)
const enabledBanners = useMemo(
() => banners?.filter((banner: BannerData) => banner.status === 'enabled') ?? [],
@ -39,14 +40,17 @@ const Banner: FC = () => {
Carousel.Plugin.Autoplay({
delay: AUTOPLAY_DELAY,
stopOnInteraction: false,
stopOnMouseEnter: true,
}),
]}
className="rounded-2xl"
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<Carousel.Content>
{enabledBanners.map((banner: BannerData) => (
<Carousel.Item key={banner.id}>
<BannerItem banner={banner} autoplayDelay={AUTOPLAY_DELAY} />
<BannerItem banner={banner} autoplayDelay={AUTOPLAY_DELAY} isPaused={isHovered} />
</Carousel.Item>
))}
</Carousel.Content>

View File

@ -8,6 +8,7 @@ type IndicatorButtonProps = {
isNextSlide: boolean
autoplayDelay: number
resetKey: number
isPaused?: boolean
onClick: () => void
}
@ -20,12 +21,12 @@ export const IndicatorButton: FC<IndicatorButtonProps> = ({
isNextSlide,
autoplayDelay,
resetKey,
isPaused = false,
onClick,
}) => {
const [progress, setProgress] = useState(0)
const [isPageVisible, setIsPageVisible] = useState(true)
const frameIdRef = useRef<number | undefined>(undefined)
const pausedTimeRef = useRef(0)
const startTimeRef = useRef(0)
// Listen to page visibility changes
@ -52,13 +53,12 @@ export const IndicatorButton: FC<IndicatorButtonProps> = ({
// reset and start new animation
setProgress(0)
startTimeRef.current = Date.now()
pausedTimeRef.current = 0
const animate = () => {
// Only continue animation when page is visible
if (!document.hidden) {
// Only continue animation when page is visible and not paused
if (!document.hidden && !isPaused) {
const now = Date.now()
const elapsed = now - startTimeRef.current - pausedTimeRef.current
const elapsed = now - startTimeRef.current
const newProgress = Math.min((elapsed / autoplayDelay) * PROGRESS_MAX, PROGRESS_MAX)
setProgress(newProgress)
@ -69,14 +69,14 @@ export const IndicatorButton: FC<IndicatorButtonProps> = ({
frameIdRef.current = requestAnimationFrame(animate)
}
}
if (!document.hidden)
if (!document.hidden && !isPaused)
frameIdRef.current = requestAnimationFrame(animate)
return () => {
if (frameIdRef.current)
cancelAnimationFrame(frameIdRef.current)
}
}, [isNextSlide, autoplayDelay, resetKey, isPageVisible])
}, [isNextSlide, autoplayDelay, resetKey, isPageVisible, isPaused])
const isActive = index === selectedIndex