video-flow-b/components/video-screen-layout.tsx
2025-07-01 17:00:02 +08:00

134 lines
4.2 KiB
TypeScript

'use client'; // Add this to ensure it's a client component
import React, { useState, useRef, useEffect } from 'react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { Button } from '@/components/ui/button';
import dynamic from 'next/dynamic';
interface VideoScreenLayoutProps {
videos: {
id: string;
url: string;
title: string;
}[];
}
function VideoScreenLayoutComponent({ 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">
{/* 视频 - Add suppressHydrationWarning to prevent className mismatch warnings */}
<video
src={video.url}
suppressHydrationWarning
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>
);
}
// Export as a client-only component to prevent hydration issues
export const VideoScreenLayout = dynamic(() => Promise.resolve(VideoScreenLayoutComponent), {
ssr: false
});