feat: 优化面试状态动画的展开和收起效果

- 添加expanding和collapsing动画类
- 使用cubic-bezier缓动函数让动画更平滑
- 收起时等待动画完成后再隐藏组件
- 动画容器添加缩放效果增强视觉体验
- 分离展开和收起动画的时间和效果

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
KQL
2025-09-08 05:39:02 +08:00
parent 01492feb5a
commit 12e3722c54
2 changed files with 51 additions and 7 deletions

View File

@@ -1,20 +1,42 @@
.interview-status-animation-wrapper { .interview-status-animation-wrapper {
width: 100%; width: 100%;
padding: 10px 0;
background: linear-gradient(135deg, #f7faff 0%, #ffffff 100%); background: linear-gradient(135deg, #f7faff 0%, #ffffff 100%);
border-top: 1px solid #e5e6eb; border-top: 1px solid #e5e6eb;
animation: slideDown 0.3s ease-out;
overflow: hidden; overflow: hidden;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
} }
@keyframes slideDown { .interview-status-animation-wrapper.expanding {
animation: expandDown 0.4s ease-out forwards;
}
.interview-status-animation-wrapper.collapsing {
animation: collapseUp 0.3s ease-in forwards;
}
@keyframes expandDown {
from { from {
max-height: 0; max-height: 0;
opacity: 0; opacity: 0;
padding: 0;
} }
to { to {
max-height: 200px; max-height: 200px;
opacity: 1; opacity: 1;
padding: 10px 0;
}
}
@keyframes collapseUp {
from {
max-height: 200px;
opacity: 1;
padding: 10px 0;
}
to {
max-height: 0;
opacity: 0;
padding: 0;
} }
} }
@@ -25,6 +47,12 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
overflow: hidden; overflow: hidden;
transform: scale(0.9);
transition: transform 0.3s ease;
}
.interview-status-animation-wrapper.expanding .animation-container {
transform: scale(1);
} }
.animation-container svg { .animation-container svg {

View File

@@ -21,6 +21,22 @@ export default ({ statusText, isOpen }) => {
const animationContainer = useRef(null); const animationContainer = useRef(null);
const lottieInstance = useRef(null); const lottieInstance = useRef(null);
const [animationData, setAnimationData] = useState(null); const [animationData, setAnimationData] = useState(null);
const [isVisible, setIsVisible] = useState(false);
const [animationClass, setAnimationClass] = useState('');
useEffect(() => {
if (isOpen) {
setIsVisible(true);
setAnimationClass('expanding');
} else if (isVisible) {
setAnimationClass('collapsing');
const timer = setTimeout(() => {
setIsVisible(false);
setAnimationClass('');
}, 300); // 等待收起动画完成
return () => clearTimeout(timer);
}
}, [isOpen, isVisible]);
useEffect(() => { useEffect(() => {
if (isOpen && statusText) { if (isOpen && statusText) {
@@ -35,7 +51,7 @@ export default ({ statusText, isOpen }) => {
}, [isOpen, statusText]); }, [isOpen, statusText]);
useEffect(() => { useEffect(() => {
if (animationData && animationContainer.current && isOpen) { if (animationData && animationContainer.current && isVisible) {
// 清除之前的动画实例 // 清除之前的动画实例
if (lottieInstance.current) { if (lottieInstance.current) {
lottieInstance.current.destroy(); lottieInstance.current.destroy();
@@ -56,12 +72,12 @@ export default ({ statusText, isOpen }) => {
} }
}; };
} }
}, [animationData, isOpen]); }, [animationData, isVisible]);
if (!isOpen) return null; if (!isVisible) return null;
return ( return (
<div className="interview-status-animation-wrapper"> <div className={`interview-status-animation-wrapper ${animationClass}`}>
<div className="animation-container" ref={animationContainer} /> <div className="animation-container" ref={animationContainer} />
</div> </div>
); );