Files
shanghai-residences/src/components/layout/clue/DialogueContent.tsx
T
2025-12-29 19:19:10 +08:00

131 lines
4.3 KiB
TypeScript

import { useState, useEffect } from "react";
import { motion } from "framer-motion";
import { Image } from "@/components";
import { getStaticImage } from "@/lib/utils";
import { DialogueClueData } from "./types";
export interface DialogueContentProps {
/** 对话线索数据 */
clue: DialogueClueData;
}
/**
* 对话内容组件
* 依次显示多条对话,所有对话同时展示,带有动画效果
*/
const DialogueContent = ({ clue }: DialogueContentProps) => {
const [visibleIndices, setVisibleIndices] = useState<number[]>([]);
// 依次显示对话:先显示第一条,延迟后显示第二条
useEffect(() => {
if (clue.dialogues.length === 0) return;
// 立即显示第一条对话
setVisibleIndices([0]);
// 延迟显示后续对话
if (clue.dialogues.length > 1) {
const timer = setTimeout(() => {
setVisibleIndices((prev) => [...prev, 1]);
}, 1500); // 第一条显示1.5秒后显示第二条
return () => clearTimeout(timer);
}
}, [clue.dialogues.length]);
return (
<div className="w-full h-full flex flex-col items-center gap-30 relative px-10">
{clue.dialogues.map((dialogue, index) => {
const isVisible = visibleIndices.includes(index);
return (
<motion.div
key={index}
initial={{ opacity: 0, y: 30, scale: 0.95 }}
animate={
isVisible
? { opacity: 1, y: 0, scale: 1 }
: { opacity: 0, y: 30, scale: 0.95 }
}
transition={{
duration: 0.6,
ease: [0.16, 1, 0.3, 1], // 自定义缓动函数,更流畅
}}
className="flex items-center gap-8 w-full max-w-7xl"
>
{/* 角色头像 */}
{dialogue.avatar && (
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={
isVisible ? { opacity: 1, x: 0 } : { opacity: 0, x: -20 }
}
transition={{
duration: 0.5,
delay: 0.1,
ease: "easeOut",
}}
className="flex-shrink-0 flex flex-col items-center"
>
<Image
src={getStaticImage(dialogue.avatar)}
className="w-60 h-60 object-contain"
alt={dialogue.character}
/>
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={
isVisible ? { opacity: 1, y: 0 } : { opacity: 0, y: 10 }
}
transition={{
duration: 0.4,
delay: 0.3,
ease: "easeOut",
}}
className="mt-3 px-5 py-2 bg-gray-800/90 rounded-lg"
>
<span className="text-white text-base font-medium">
{dialogue.character}
</span>
</motion.div>
</motion.div>
)}
{/* 对话气泡 */}
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={isVisible ? { opacity: 1, x: 0 } : { opacity: 0, x: 20 }}
transition={{
duration: 0.5,
delay: 0.2,
ease: "easeOut",
}}
className="flex-1 relative"
>
<div className="bg-gray-800/95 rounded-3xl px-20 py-8 shadow-2xl">
<motion.div
initial={{ opacity: 0 }}
animate={isVisible ? { opacity: 1 } : { opacity: 0 }}
transition={{
duration: 0.4,
delay: 0.4,
}}
className="text-white text-4xl leading-relaxed"
>
{dialogue.text}
</motion.div>
</div>
{/* 对话气泡箭头 - 指向左侧头像 */}
<div className="absolute left-0 top-1/2 -translate-y-1/2 -translate-x-3">
<div className="w-0 h-0 border-t-[12px] border-t-transparent border-r-[12px] border-r-gray-800/95 border-b-[12px] border-b-transparent" />
</div>
</motion.div>
</motion.div>
);
})}
</div>
);
};
export default DialogueContent;