feat:init
This commit is contained in:
1 parent
53d0eeac6b
commit
ae0c050010
494 files changed
+17701
-2318
No files matched your search
@@ -0,0 +1,130 @@
|
||||
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;
|
||||
Reference in new issue
Block a user