mirror of
https://github.com/MoonTechLab/LunaTV.git
synced 2026-02-21 09:14:42 +08:00
style(VideoCard): 优化卡片悬停动画和交互效果
改进悬停时的缩放、阴影和过渡动画效果 调整按钮的延迟和缓动效果 优化图片加载动画和评分徽章样式
This commit is contained in:
@@ -49,13 +49,10 @@ export default function VideoCard({
|
||||
|
||||
const isAggregate = from === 'search' && !!items?.length;
|
||||
|
||||
// 聚合数据(仅在 search 模式下)
|
||||
const aggregateData = useMemo(() => {
|
||||
if (!isAggregate || !items) return null;
|
||||
|
||||
const countMap = new Map<string | number, number>();
|
||||
const episodeCountMap = new Map<number, number>();
|
||||
|
||||
items.forEach((item) => {
|
||||
if (item.douban_id && item.douban_id !== 0) {
|
||||
countMap.set(item.douban_id, (countMap.get(item.douban_id) || 0) + 1);
|
||||
@@ -121,9 +118,7 @@ export default function VideoCard({
|
||||
async (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (from === 'douban' || !actualSource || !actualId) return;
|
||||
|
||||
try {
|
||||
const newState = await toggleFavorite(actualSource, actualId, {
|
||||
title: actualTitle,
|
||||
@@ -154,9 +149,7 @@ export default function VideoCard({
|
||||
async (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (from !== 'playrecord' || !actualSource || !actualId) return;
|
||||
|
||||
try {
|
||||
await deletePlayRecord(actualSource, actualId);
|
||||
onDelete?.();
|
||||
@@ -237,119 +230,119 @@ export default function VideoCard({
|
||||
|
||||
return (
|
||||
<div
|
||||
className='group relative w-full rounded-lg bg-transparent'
|
||||
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'
|
||||
onClick={handleClick}
|
||||
>
|
||||
{/* 海报容器 */}
|
||||
<div className='relative aspect-[2/3] overflow-hidden rounded-lg'>
|
||||
{/* 骨架屏 */}
|
||||
{!isLoaded && <ImagePlaceholder aspectRatio='aspect-[2/3]' />}
|
||||
<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' />
|
||||
)}
|
||||
|
||||
{/* 图片加载动画 */}
|
||||
{/* 图片加载动画 - 改进淡入和锐化效果 */}
|
||||
<Image
|
||||
src={actualPoster}
|
||||
alt={actualTitle}
|
||||
fill
|
||||
className={`object-cover transition-all duration-700 ease-out ${
|
||||
isLoaded ? 'opacity-100 scale-100' : 'opacity-0 scale-95 blur-sm'
|
||||
} group-hover:scale-105`}
|
||||
isLoaded ? 'opacity-100 blur-0' : 'opacity-0 blur-md'
|
||||
}`}
|
||||
onLoadingComplete={() => setIsLoaded(true)}
|
||||
referrerPolicy='no-referrer'
|
||||
priority={false}
|
||||
/>
|
||||
|
||||
{/* 悬浮层 */}
|
||||
<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 flex items-center justify-center cursor-pointer'>
|
||||
{config.showPlayButton && (
|
||||
{/* 悬浮层 - 改进渐变和元素过渡 */}
|
||||
<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>
|
||||
|
||||
{/* 播放按钮 */}
|
||||
{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'>
|
||||
<PlayCircleIcon
|
||||
size={52}
|
||||
strokeWidth={1}
|
||||
className='rounded-full cursor-pointer play-icon-fixed'
|
||||
size={50}
|
||||
strokeWidth={0.8}
|
||||
className='rounded-full text-white hover:fill-green-500 transition-all duration-200 ease-out hover:scale-110'
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 已看 / 收藏按钮 */}
|
||||
{(config.showHeart || config.showCheckCircle) && (
|
||||
<div className='absolute bottom-3 right-3 flex items-center gap-3 transform translate-y-2 opacity-0 group-hover:translate-y-0 group-hover:opacity-100 transition-all duration-300 ease-out'>
|
||||
{config.showCheckCircle && (
|
||||
<CheckCircle
|
||||
onClick={handleDeleteRecord}
|
||||
size={20}
|
||||
className='rounded-full transition-all duration-300ms transform hover:scale-110 text-white hover:stroke-green-500'
|
||||
aria-label='标记为已看'
|
||||
/>
|
||||
)}
|
||||
{/* 已看 / 收藏按钮 - 改进延迟和缓动效果 */}
|
||||
{(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'>
|
||||
{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'
|
||||
/>
|
||||
)}
|
||||
{config.showHeart && (
|
||||
<Heart
|
||||
onClick={handleToggleFavorite}
|
||||
size={20}
|
||||
className={`rounded-full transition-all duration-300 ease-out hover:scale-110 ${
|
||||
favorited
|
||||
? 'fill-red-600 stroke-red-600 animate-pulse-subtle'
|
||||
: 'fill-transparent stroke-white hover:stroke-red-400'
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{config.showHeart && (
|
||||
<Heart
|
||||
onClick={handleToggleFavorite}
|
||||
size={20}
|
||||
className={`rounded-full transition-all duration-300ms transform hover:scale-110 ${
|
||||
favorited
|
||||
? 'fill-red-600 stroke-red-600'
|
||||
: 'fill-transparent stroke-white hover:stroke-red-400'
|
||||
}`}
|
||||
aria-label={favorited ? '取消收藏' : '加入收藏'}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 评分徽章 */}
|
||||
{/* 评分徽章 - 添加弹出效果 */}
|
||||
{config.showRating && rate && (
|
||||
<div className='absolute top-2 right-2 bg-pink-500 text-white text-xs font-bold w-6 h-6 sm:w-7 sm:h-7 rounded-full flex items-center justify-center shadow-md transform transition-transform duration-300 group-hover:scale-110'>
|
||||
<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'>
|
||||
{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 transform transition-transform duration-300 group-hover:scale-105'>
|
||||
<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 transform transition-transform duration-300 group-hover:scale-105'>
|
||||
<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>
|
||||
)}
|
||||
|
||||
{/* 豆瓣链接按钮 */}
|
||||
{/* 豆瓣链接按钮 - 改进进入方向和延迟 */}
|
||||
{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-2 group-hover:translate-x-0 group-hover:opacity-100 group-hover:scale-110 transition-all duration-300'
|
||||
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'
|
||||
>
|
||||
<div className='bg-green-500 text-white text-xs font-bold p-1 rounded-full flex-center shadow-md hover:bg-green-600 hover:scale-110 transition-all duration-300'>
|
||||
<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'>
|
||||
<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='h-full bg-green-500 rounded-full transition-all duration-300'
|
||||
className='h-full bg-green-500 rounded-full transition-all duration-1000 ease-out'
|
||||
style={{ width: `${progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 标题与来源信息 */}
|
||||
<span className='mt-2 block text-center text-sm font-semibold truncate text-gray-900 dark:text-gray-100 transition-colors duration-300 group-hover:text-green-600 dark:group-hover:text-green-400'>
|
||||
{/* 标题与来源信息 - 改进颜色过渡和延迟 */}
|
||||
<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'>
|
||||
{actualTitle}
|
||||
</span>
|
||||
|
||||
{config.showSourceName && source_name && (
|
||||
<span className='block text-center text-xs text-gray-500 dark:text-gray-400 mt-1 transition-all duration-300 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 group-hover:border-green-500/60'>
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user