forked from 77media/video-flow
125 lines
3.8 KiB
TypeScript
125 lines
3.8 KiB
TypeScript
import React, { useState, useRef, useEffect } from 'react';
|
|
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
interface VideoScreenLayoutProps {
|
|
videos: {
|
|
id: string;
|
|
url: string;
|
|
title: string;
|
|
}[];
|
|
}
|
|
|
|
export function VideoScreenLayout({ videos }: VideoScreenLayoutProps) {
|
|
const [currentIndex, setCurrentIndex] = useState(0);
|
|
const [isAnimating, setIsAnimating] = useState(false);
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
|
|
// 计算每个面板的样式
|
|
const getPanelStyle = (index: number) => {
|
|
const position = index - currentIndex;
|
|
const scale = Math.max(0.6, 1 - Math.abs(position) * 0.2);
|
|
const zIndex = 10 - Math.abs(position);
|
|
const opacity = Math.max(0.4, 1 - Math.abs(position) * 0.3);
|
|
|
|
let transform = `
|
|
perspective(1000px)
|
|
scale(${scale})
|
|
translateX(${position * 100}%)
|
|
`;
|
|
|
|
// 添加侧面板的 3D 旋转效果
|
|
if (position !== 0) {
|
|
const rotateY = position > 0 ? -15 : 15;
|
|
transform += ` rotateY(${rotateY}deg)`;
|
|
}
|
|
|
|
return {
|
|
transform,
|
|
zIndex,
|
|
opacity,
|
|
};
|
|
};
|
|
|
|
// 处理切换
|
|
const handleSlide = (direction: 'prev' | 'next') => {
|
|
if (isAnimating) return;
|
|
|
|
setIsAnimating(true);
|
|
const newIndex = direction === 'next'
|
|
? (currentIndex + 1) % videos.length
|
|
: (currentIndex - 1 + videos.length) % videos.length;
|
|
|
|
setCurrentIndex(newIndex);
|
|
|
|
// 动画结束后重置状态
|
|
setTimeout(() => setIsAnimating(false), 500);
|
|
};
|
|
|
|
return (
|
|
<div className="relative w-full h-[600px] overflow-hidden bg-[var(--background)]">
|
|
{/* 视频面板容器 */}
|
|
<div
|
|
ref={containerRef}
|
|
className="absolute inset-0 flex items-center justify-center"
|
|
style={{
|
|
perspective: '1000px',
|
|
transformStyle: 'preserve-3d',
|
|
}}
|
|
>
|
|
{videos.map((video, index) => (
|
|
<div
|
|
key={video.id}
|
|
className="absolute w-[640px] h-[360px] transition-all duration-500 ease-out"
|
|
style={getPanelStyle(index)}
|
|
>
|
|
<div className="relative w-full h-full overflow-hidden rounded-lg">
|
|
{/* 视频 */}
|
|
<video
|
|
src={video.url}
|
|
className="w-full h-full object-cover"
|
|
autoPlay
|
|
loop
|
|
muted
|
|
playsInline
|
|
/>
|
|
|
|
{/* 视频标题 - 只在中间面板显示 */}
|
|
{index === currentIndex && (
|
|
<div className="absolute bottom-0 left-0 right-0 p-4 bg-gradient-to-t from-black/80 to-transparent">
|
|
<h3 className="text-white text-lg font-medium">{video.title}</h3>
|
|
</div>
|
|
)}
|
|
|
|
{/* 玻璃态遮罩 - 侧面板半透明效果 */}
|
|
{index !== currentIndex && (
|
|
<div className="absolute inset-0 bg-black/20 backdrop-blur-sm" />
|
|
)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* 切换按钮 */}
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="absolute left-4 top-1/2 -translate-y-1/2 w-12 h-12 rounded-full bg-white/10 hover:bg-white/20 backdrop-blur-sm transition-all"
|
|
onClick={() => handleSlide('prev')}
|
|
disabled={isAnimating}
|
|
>
|
|
<ChevronLeft className="w-6 h-6 text-white" />
|
|
</Button>
|
|
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="absolute right-4 top-1/2 -translate-y-1/2 w-12 h-12 rounded-full bg-white/10 hover:bg-white/20 backdrop-blur-sm transition-all"
|
|
onClick={() => handleSlide('next')}
|
|
disabled={isAnimating}
|
|
>
|
|
<ChevronRight className="w-6 h-6 text-white" />
|
|
</Button>
|
|
</div>
|
|
);
|
|
}
|