mirror of
https://github.com/MoonTechLab/LunaTV.git
synced 2026-02-22 02:24:44 +08:00
refactor(组件): 优化滚动行和视频卡的样式与动画效果
- 调整 ScrollableRow 的内边距样式 - 简化 ImagePlaceholder 的类名并修改圆角大小 - 重构 VideoCard 的悬停动画和交互效果,改进加载状态处理 - 统一过渡动画的缓动函数和持续时间
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// 图片占位符组件 - 实现骨架屏效果(支持暗色模式)
|
||||
const ImagePlaceholder = ({ aspectRatio }: { aspectRatio: string }) => (
|
||||
<div
|
||||
className={`w-full ${aspectRatio} rounded-md overflow-hidden transition-opacity duration-500`}
|
||||
className={`w-full ${aspectRatio} rounded-lg`}
|
||||
style={{
|
||||
background:
|
||||
'linear-gradient(90deg, var(--skeleton-color) 25%, var(--skeleton-highlight) 50%, var(--skeleton-color) 75%)',
|
||||
|
||||
@@ -102,7 +102,7 @@ export default function ScrollableRow({
|
||||
>
|
||||
<div
|
||||
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}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -54,7 +54,7 @@ export default function VideoCard({
|
||||
}: VideoCardProps) {
|
||||
const router = useRouter();
|
||||
const [favorited, setFavorited] = useState(false);
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const isAggregate = from === 'search' && !!items?.length;
|
||||
|
||||
@@ -254,130 +254,115 @@ export default function VideoCard({
|
||||
|
||||
return (
|
||||
<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}
|
||||
>
|
||||
{/* 海报容器 */}
|
||||
<div className='relative aspect-[2/3] overflow-hidden rounded-lg shadow-md transition-shadow duration-300 ease-out group-hover:shadow-xl'>
|
||||
{/* 骨架屏 - 添加渐入动画 */}
|
||||
{!isLoaded && (
|
||||
<ImagePlaceholder aspectRatio='aspect-[2/3] transition-opacity duration-500 ease-out' />
|
||||
)}
|
||||
|
||||
{/* 图片加载动画 - 改进淡入和锐化效果 */}
|
||||
<div className='relative aspect-[2/3] overflow-hidden rounded-lg'>
|
||||
{/* 骨架屏 */}
|
||||
{!isLoading && <ImagePlaceholder aspectRatio='aspect-[2/3]' />}
|
||||
{/* 图片 */}
|
||||
<Image
|
||||
src={processImageUrl(actualPoster)}
|
||||
alt={actualTitle}
|
||||
fill
|
||||
className={`object-cover transition-all duration-700 ease-out ${
|
||||
isLoaded ? 'opacity-100 blur-0' : 'opacity-0 blur-md'
|
||||
}`}
|
||||
onLoadingComplete={() => setIsLoaded(true)}
|
||||
className='object-cover'
|
||||
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 && (
|
||||
<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
|
||||
size={50}
|
||||
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>
|
||||
)}
|
||||
|
||||
{/* 已看 / 收藏按钮 - 改进延迟和缓动效果 */}
|
||||
{/* 操作按钮 */}
|
||||
{(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 && (
|
||||
<CheckCircle
|
||||
onClick={handleDeleteRecord}
|
||||
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 && (
|
||||
<Heart
|
||||
onClick={handleToggleFavorite}
|
||||
size={20}
|
||||
className={`rounded-full transition-all duration-300 ease-out hover:scale-110 ${
|
||||
className={`transition-all duration-300 ease-out ${
|
||||
favorited
|
||||
? 'fill-red-600 stroke-red-600 animate-pulse-subtle'
|
||||
? 'fill-red-600 stroke-red-600'
|
||||
: 'fill-transparent stroke-white hover:stroke-red-400'
|
||||
}`}
|
||||
} hover:scale-[1.1]`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 评分徽章 - 添加弹出效果 */}
|
||||
{/* 徽章 */}
|
||||
{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}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 集数徽章 - 改进缩放和阴影 */}
|
||||
{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'>
|
||||
{currentEpisode}/{actualEpisodes}
|
||||
</div>
|
||||
)}
|
||||
{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}
|
||||
{actualEpisodes && actualEpisodes > 1 && (
|
||||
<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'>
|
||||
{currentEpisode
|
||||
? `${currentEpisode}/${actualEpisodes}`
|
||||
: actualEpisodes}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 豆瓣链接按钮 - 改进进入方向和延迟 */}
|
||||
{/* 豆瓣链接 */}
|
||||
{config.showDoubanLink && actualDoubanId && (
|
||||
<a
|
||||
href={`https://movie.douban.com/subject/${actualDoubanId}`}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
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} />
|
||||
</div>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 进度条 - 添加动画效果 */}
|
||||
{/* 进度条 */}
|
||||
{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
|
||||
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}%` }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 标题与来源信息 - 改进颜色过渡和延迟 */}
|
||||
<div className='relative'>
|
||||
<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'>
|
||||
{/* 标题与来源 */}
|
||||
<div className='mt-2 text-center'>
|
||||
<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}
|
||||
</span>
|
||||
{/* 自定义 tooltip */}
|
||||
<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'>
|
||||
{actualTitle}
|
||||
<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>
|
||||
</div>
|
||||
</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}
|
||||
{config.showSourceName && source_name && (
|
||||
<span className='block text-xs text-gray-500 dark:text-gray-400 mt-1'>
|
||||
<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'>
|
||||
{source_name}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user