refactor(组件): 优化滚动行和视频卡的样式与动画效果

- 调整 ScrollableRow 的内边距样式
- 简化 ImagePlaceholder 的类名并修改圆角大小
- 重构 VideoCard 的悬停动画和交互效果,改进加载状态处理
- 统一过渡动画的缓动函数和持续时间
This commit is contained in:
SongPro
2025-07-15 11:27:58 +08:00
parent 656c1c256f
commit f187e56e2e
3 changed files with 43 additions and 58 deletions

View File

@@ -1,7 +1,7 @@
// 图片占位符组件 - 实现骨架屏效果(支持暗色模式) // 图片占位符组件 - 实现骨架屏效果(支持暗色模式)
const ImagePlaceholder = ({ aspectRatio }: { aspectRatio: string }) => ( const ImagePlaceholder = ({ aspectRatio }: { aspectRatio: string }) => (
<div <div
className={`w-full ${aspectRatio} rounded-md overflow-hidden transition-opacity duration-500`} className={`w-full ${aspectRatio} rounded-lg`}
style={{ style={{
background: background:
'linear-gradient(90deg, var(--skeleton-color) 25%, var(--skeleton-highlight) 50%, var(--skeleton-color) 75%)', 'linear-gradient(90deg, var(--skeleton-color) 25%, var(--skeleton-highlight) 50%, var(--skeleton-color) 75%)',

View File

@@ -102,7 +102,7 @@ export default function ScrollableRow({
> >
<div <div
ref={containerRef} ref={containerRef}
className='flex space-x-6 overflow-x-auto scrollbar-hide py-1 sm:py-2 pb-12 sm:pb-14' className='flex space-x-6 overflow-x-auto scrollbar-hide py-1 sm:py-2 pb-12 sm:pb-14 px-4 sm:px-6'
onScroll={checkScroll} onScroll={checkScroll}
> >
{children} {children}

View File

@@ -54,7 +54,7 @@ export default function VideoCard({
}: VideoCardProps) { }: VideoCardProps) {
const router = useRouter(); const router = useRouter();
const [favorited, setFavorited] = useState(false); const [favorited, setFavorited] = useState(false);
const [isLoaded, setIsLoaded] = useState(false); const [isLoading, setIsLoading] = useState(false);
const isAggregate = from === 'search' && !!items?.length; const isAggregate = from === 'search' && !!items?.length;
@@ -254,130 +254,115 @@ export default function VideoCard({
return ( return (
<div <div
className='group relative w-full rounded-lg bg-transparent transition-all duration-300 ease-out hover:-translate-y-1 hover:scale-[1.02] cursor-pointer' className='group relative w-full rounded-lg bg-transparent cursor-pointer transition-all duration-300 ease-in-out hover:scale-[1.05]'
onClick={handleClick} onClick={handleClick}
> >
{/* 海报容器 */} {/* 海报容器 */}
<div className='relative aspect-[2/3] overflow-hidden rounded-lg shadow-md transition-shadow duration-300 ease-out group-hover:shadow-xl'> <div className='relative aspect-[2/3] overflow-hidden rounded-lg'>
{/* 骨架屏 - 添加渐入动画 */} {/* 骨架屏 */}
{!isLoaded && ( {!isLoading && <ImagePlaceholder aspectRatio='aspect-[2/3]' />}
<ImagePlaceholder aspectRatio='aspect-[2/3] transition-opacity duration-500 ease-out' /> {/* 图片 */}
)}
{/* 图片加载动画 - 改进淡入和锐化效果 */}
<Image <Image
src={processImageUrl(actualPoster)} src={processImageUrl(actualPoster)}
alt={actualTitle} alt={actualTitle}
fill fill
className={`object-cover transition-all duration-700 ease-out ${ className='object-cover'
isLoaded ? 'opacity-100 blur-0' : 'opacity-0 blur-md'
}`}
onLoadingComplete={() => setIsLoaded(true)}
referrerPolicy='no-referrer' referrerPolicy='no-referrer'
priority={false} onLoadingComplete={() => setIsLoading(true)}
/> />
{/* 悬浮层 - 改进渐变和元素过渡 */} {/* 悬浮遮罩 */}
<div className='absolute inset-0 bg-gradient-to-t from-black/80 via-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 ease-out pointer-events-auto'></div> <div className='absolute inset-0 bg-gradient-to-t from-black/80 via-black/20 to-transparent opacity-0 transition-opacity duration-300 ease-in-out group-hover:opacity-100' />
{/* 播放按钮 */} {/* 播放按钮 */}
{config.showPlayButton && ( {config.showPlayButton && (
<div className='absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-all duration-200 pointer-events-auto'> <div className='absolute inset-0 flex items-center justify-center opacity-0 transition-all duration-300 ease-in-out delay-75 group-hover:opacity-100 group-hover:scale-100'>
<PlayCircleIcon <PlayCircleIcon
size={50} size={50}
strokeWidth={0.8} strokeWidth={0.8}
className='rounded-full text-white fill-transparent transition-[fill,transform] duration-200 ease-out [-webkit-transform:translateZ(0)] [-webkit-backface-visibility:hidden] [backface-visibility:hidden] [will-change:transform,fill,opacity] hover:fill-green-500 hover:scale-110' className='text-white fill-transparent transition-all duration-300 ease-out hover:fill-green-500 hover:scale-[1.1]'
/> />
</div> </div>
)} )}
{/* 已看 / 收藏按钮 - 改进延迟和缓动效果 */} {/* 操作按钮 */}
{(config.showHeart || config.showCheckCircle) && ( {(config.showHeart || config.showCheckCircle) && (
<div className='absolute bottom-3 right-3 flex items-center gap-3 translate-y-4 opacity-0 group-hover:translate-y-0 group-hover:opacity-100 transition-all duration-300 ease-out delay-75 pointer-events-auto'> <div className='absolute bottom-3 right-3 flex gap-3 opacity-0 translate-y-2 transition-all duration-300 ease-in-out group-hover:opacity-100 group-hover:translate-y-0'>
{config.showCheckCircle && ( {config.showCheckCircle && (
<CheckCircle <CheckCircle
onClick={handleDeleteRecord} onClick={handleDeleteRecord}
size={20} size={20}
className='rounded-full text-white hover:stroke-green-500 transition-transform duration-200 ease-out hover:scale-110' className='text-white transition-all duration-300 ease-out hover:stroke-green-500 hover:scale-[1.1]'
/> />
)} )}
{config.showHeart && ( {config.showHeart && (
<Heart <Heart
onClick={handleToggleFavorite} onClick={handleToggleFavorite}
size={20} size={20}
className={`rounded-full transition-all duration-300 ease-out hover:scale-110 ${ className={`transition-all duration-300 ease-out ${
favorited favorited
? 'fill-red-600 stroke-red-600 animate-pulse-subtle' ? 'fill-red-600 stroke-red-600'
: 'fill-transparent stroke-white hover:stroke-red-400' : 'fill-transparent stroke-white hover:stroke-red-400'
}`} } hover:scale-[1.1]`}
/> />
)} )}
</div> </div>
)} )}
{/* 评分徽章 - 添加弹出效果 */} {/* 徽章 */}
{config.showRating && rate && ( {config.showRating && rate && (
<div className='absolute top-2 right-2 bg-pink-500 text-white text-xs font-bold p-1 sm:w-7 sm:h-7 rounded-full flex items-center justify-center shadow-md transition-all duration-300 ease-out transform scale-90 group-hover:scale-110 group-hover:shadow-lg'> <div className='absolute top-2 right-2 bg-pink-500 text-white text-xs font-bold w-7 h-7 rounded-full flex items-center justify-center shadow-md transition-all duration-300 ease-out group-hover:scale-110'>
{rate} {rate}
</div> </div>
)} )}
{/* 集数徽章 - 改进缩放和阴影 */} {actualEpisodes && actualEpisodes > 1 && (
{actualEpisodes && actualEpisodes > 1 && currentEpisode && ( <div className='absolute top-2 right-2 bg-green-500 text-white text-xs font-semibold px-2 py-1 rounded-md shadow-md transition-all duration-300 ease-out group-hover:scale-110'>
<div className='absolute top-2 right-2 bg-green-500 text-white text-xs font-semibold rounded-md px-2 py-1 shadow-md transition-all duration-300 ease-out transform scale-90 group-hover:scale-105 group-hover:shadow-lg'> {currentEpisode
{currentEpisode}/{actualEpisodes} ? `${currentEpisode}/${actualEpisodes}`
</div> : actualEpisodes}
)}
{actualEpisodes && actualEpisodes > 1 && !currentEpisode && (
<div className='absolute top-2 right-2 bg-green-500 text-white text-xs font-semibold rounded-md px-2 py-1 shadow-md transition-all duration-300 ease-out transform scale-90 group-hover:scale-105 group-hover:shadow-lg'>
{actualEpisodes}
</div> </div>
)} )}
{/* 豆瓣链接按钮 - 改进进入方向和延迟 */} {/* 豆瓣链接 */}
{config.showDoubanLink && actualDoubanId && ( {config.showDoubanLink && actualDoubanId && (
<a <a
href={`https://movie.douban.com/subject/${actualDoubanId}`} href={`https://movie.douban.com/subject/${actualDoubanId}`}
target='_blank' target='_blank'
rel='noopener noreferrer' rel='noopener noreferrer'
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
className='absolute top-2 left-2 opacity-0 -translate-x-4 group-hover:translate-x-0 group-hover:opacity-100 transition-all duration-300 ease-out delay-100' className='absolute top-2 left-2 opacity-0 -translate-x-2 transition-all duration-300 ease-in-out delay-100 group-hover:opacity-100 group-hover:translate-x-0'
> >
<div className='bg-green-500 text-white text-xs font-bold p-1 sm:w-7 sm:h-7 rounded-full flex items-center justify-center shadow-md hover:bg-green-600 hover:scale-110 transition-all duration-200 ease-out'> <div className='bg-green-500 text-white text-xs font-bold w-7 h-7 rounded-full flex items-center justify-center shadow-md hover:bg-green-600 hover:scale-[1.1] transition-all duration-300 ease-out'>
<Link size={16} /> <Link size={16} />
</div> </div>
</a> </a>
)} )}
</div> </div>
{/* 进度条 - 添加动画效果 */} {/* 进度条 */}
{config.showProgress && progress !== undefined && ( {config.showProgress && progress !== undefined && (
<div className='mt-1 h-1 w-full bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden'> <div className='mt-1 h-1 w-full bg-gray-200 rounded-full overflow-hidden'>
<div <div
className='h-full bg-green-500 rounded-full transition-all duration-1000 ease-out' className='h-full bg-green-500 transition-all duration-500 ease-out'
style={{ width: `${progress}%` }} style={{ width: `${progress}%` }}
/> />
</div> </div>
)} )}
{/* 标题与来源信息 - 改进颜色过渡和延迟 */} {/* 标题与来源 */}
<div className='relative'> <div className='mt-2 text-center'>
<span className='mt-2 block text-center text-sm font-semibold truncate text-gray-900 dark:text-gray-100 transition-all duration-300 ease-out group-hover:text-green-600 dark:group-hover:text-green-400 peer'> <span className='block text-sm font-semibold truncate text-gray-900 dark:text-gray-100 transition-colors duration-300 ease-in-out group-hover:text-green-600 dark:group-hover:text-green-400'>
{actualTitle} {actualTitle}
</span> </span>
{/* 自定义 tooltip */} {config.showSourceName && source_name && (
<div className='absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-1 bg-gray-800 text-white text-xs rounded-md shadow-lg opacity-0 invisible peer-hover:opacity-100 peer-hover:visible transition-all duration-200 ease-out delay-100 whitespace-nowrap z-50 pointer-events-none'> <span className='block text-xs text-gray-500 dark:text-gray-400 mt-1'>
{actualTitle} <span className='inline-block border rounded px-2 py-0.5 border-gray-500/60 dark:border-gray-400/60 transition-all duration-300 ease-in-out group-hover:border-green-500/60 group-hover:text-green-600 dark:group-hover:text-green-400'>
<div className='absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent border-t-gray-800'></div> {source_name}
</div> </span>
</div>
{config.showSourceName && source_name && (
<span className='block text-center text-xs text-gray-500 dark:text-gray-400 mt-1 transition-all duration-300 ease-out delay-75 group-hover:text-green-500 dark:group-hover:text-green-500 group-hover:scale-105'>
<span className='inline-block border rounded px-2 py-0.5 border-gray-500/60 dark:border-gray-400/60 transition-all duration-300 ease-out group-hover:border-green-500/60'>
{source_name}
</span> </span>
</span> )}
)} </div>
</div> </div>
); );
} }