94 lines
2.0 KiB
TypeScript
94 lines
2.0 KiB
TypeScript
import React from 'react';
|
|
import {
|
|
AbsoluteFill,
|
|
interpolate,
|
|
useCurrentFrame,
|
|
useVideoConfig,
|
|
} from 'remotion';
|
|
|
|
interface TitleProps {
|
|
title: string;
|
|
duration?: number; // 标题显示时长(秒)
|
|
fadeOutStart?: number; // 开始淡出的时间(秒)
|
|
}
|
|
|
|
/**
|
|
* 片头标题组件
|
|
* 在视频顶部显示标题,带淡入淡出效果
|
|
*/
|
|
export const Title: React.FC<TitleProps> = ({
|
|
title,
|
|
duration = 3,
|
|
fadeOutStart = 2,
|
|
}) => {
|
|
const frame = useCurrentFrame();
|
|
const { fps } = useVideoConfig();
|
|
|
|
const currentTimeInSeconds = frame / fps;
|
|
|
|
// 如果超过显示时长,不渲染
|
|
if (currentTimeInSeconds > duration) {
|
|
return null;
|
|
}
|
|
|
|
// 淡入效果 (0-0.5秒)
|
|
const fadeInOpacity = interpolate(
|
|
currentTimeInSeconds,
|
|
[0, 0.5],
|
|
[0, 1],
|
|
{ extrapolateRight: 'clamp' }
|
|
);
|
|
|
|
// 淡出效果
|
|
const fadeOutOpacity = interpolate(
|
|
currentTimeInSeconds,
|
|
[fadeOutStart, duration],
|
|
[1, 0],
|
|
{ extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }
|
|
);
|
|
|
|
const opacity = Math.min(fadeInOpacity, fadeOutOpacity);
|
|
|
|
// 轻微的下滑动画
|
|
const translateY = interpolate(
|
|
currentTimeInSeconds,
|
|
[0, 0.5],
|
|
[-20, 0],
|
|
{ extrapolateRight: 'clamp' }
|
|
);
|
|
|
|
return (
|
|
<AbsoluteFill
|
|
style={{
|
|
justifyContent: 'flex-start',
|
|
alignItems: 'center',
|
|
paddingTop: '6%',
|
|
opacity,
|
|
}}
|
|
>
|
|
<h1
|
|
style={{
|
|
transform: `translateY(${translateY}px)`,
|
|
textAlign: 'center',
|
|
color: '#FFFFFF',
|
|
fontSize: '72px',
|
|
fontWeight: 900,
|
|
fontFamily: '"PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif',
|
|
textShadow: `
|
|
0 0 10px rgba(0,0,0,0.9),
|
|
0 0 20px rgba(0,0,0,0.7),
|
|
0 4px 8px rgba(0,0,0,0.8),
|
|
0 8px 16px rgba(0,0,0,0.5)
|
|
`,
|
|
margin: 0,
|
|
padding: '0 5%',
|
|
lineHeight: 1.3,
|
|
letterSpacing: '4px',
|
|
}}
|
|
>
|
|
{title}
|
|
</h1>
|
|
</AbsoluteFill>
|
|
);
|
|
};
|