React实现卡片容器切换时的高度动画效果求助
实现履历卡片高度切换的平滑动画效果
你当前代码里给#experience-container加了transition: height 0.3s ease-out,但因为容器高度默认是auto,CSS无法对auto值执行过渡动画。要实现高度变化的平滑效果,需要通过JS动态计算内容高度并设置给容器,具体修改如下:
步骤1:修改React组件代码
添加ref获取描述内容高度,新增状态管理容器高度:
import React, { useState, useRef, useEffect } from 'react'; import './Experience.css'; const experiences = [ { title: '软件开发实习生', date: '2020年8月 - 2020年9月', points: [ '填充文本 填充文本 填充文本 填充文本 填充文本 填充文本 填充文本', '填充文本 填充文本 填充文本 填充文本 填充文本 填充文本 填充文本', '填充文本 填充文本 填充文本 填充文本 填充文本 填充文本 填充文本', '填充文本 填充文本 填充文本 填充文本 填充文本 填充文本 填充文本', '填充文本 填充文本 填充文本 填充文本 填充文本 填充文本 填充文本', '填充文本 填充文本 填充文本 填充文本 填充文本 填充文本 填充文本', ], }, { title: '前端工程师', date: '2020年10月 - 2020年12月', points: [ '填充文本 填充文本 填充文本 填充文本 填充文本 填充文本 填充文本', '填充文本 填充文本 填充文本 填充文本 填充文本 填充文本 填充文本', '填充文本 填充文本 填充文本 填充文本 填充文本 填充文本 填充文本', '填充文本 填充文本 填充文本 填充文本 填充文本 填充文本 填充文本', '填充文本 填充文本 填充文本 填充文本 填充文本 填充文本 填充文本', ], }, { title: '全栈开发者', date: '2021年1月 - 至今', points: [ '填充文本 填充文本 填充文本 填充文本 填充文本 填充文本 填充文本', '填充文本 填充文本 填充文本 填充文本 填充文本 填充文本 填充文本', '填充文本 填充文本 填充文本 填充文本 填充文本 填充文本 填充文本', ], }, ]; const Experience = () => { const [selectedExperience, setSelectedExperience] = useState(experiences[0]); const [clickedIndex, setClickedIndex] = useState(null); const [fadeIn, setFadeIn] = useState(true); // 新增状态:控制容器高度 const [containerHeight, setContainerHeight] = useState('auto'); // 获取描述区域和容器的ref const descriptionRef = useRef(null); const containerRef = useRef(null); const handleTitleClick = (experience, index) => { // 先记录当前容器的固定高度 const currentHeight = containerRef.current.offsetHeight; setContainerHeight(`${currentHeight}px`); setFadeIn(false); setClickedIndex(index); setTimeout(() => { setSelectedExperience(experience); // 强制重绘后计算新内容高度 requestAnimationFrame(() => { const titleBarHeight = containerRef.current.querySelector('.experience-titles').offsetHeight; const newContentHeight = descriptionRef.current.offsetHeight + titleBarHeight + 40; // 加上内边距 setContainerHeight(`${newContentHeight}px`); setFadeIn(true); }); }, 250); }; // 初始化时计算初始高度 useEffect(() => { const titleBarHeight = containerRef.current.querySelector('.experience-titles').offsetHeight; const initialHeight = descriptionRef.current.offsetHeight + titleBarHeight + 40; setContainerHeight(`${initialHeight}px`); }, []); return ( <React.Fragment> <div id="experience-card"> <div id="experience-header"> <span className="experience-highlighted-text">工作经历</span> </div> <div id="experience-container" ref={containerRef} style={{ height: containerHeight }} > <div className="experience-titles"> {experiences.map((experience, index) => ( <div key={index} className={`experience-title ${clickedIndex === index ? 'clicked' : ''}`} onClick={() => handleTitleClick(experience, index)} > {experience.title} </div> ))} </div> <div className={fadeIn ? 'experience-description fade-in' : 'experience-description'} ref={descriptionRef} > <h3 className="experience-position">{selectedExperience.title}</h3> <p className="experience-date">{selectedExperience.date}</p> {selectedExperience.points && ( <ul className="experience-points"> {selectedExperience.points.map((point, index) => ( <li key={index}>{point}</li> ))} </ul> )} </div> </div> </div> </React.Fragment> ); }; export default Experience;
步骤2:调整CSS代码
移除容器默认高度设置,确保过渡效果生效:
#experience-container { width: 60%; margin: auto; background-color: rgba(255, 255, 255, 0.8); border-radius: 10px; padding: 20px; box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); overflow: hidden; transition: height 0.3s ease-out; /* 移除默认height,由JS动态设置 */ } #experience-header { text-align: center; font-family: 'Montserrat', sans-serif; font-weight: bolder; font-size: 40px; z-index: 1; position: relative; margin-bottom: 40px; margin-top: 100px; } .experience-highlighted-text { position: relative; z-index: 1; } .experience-highlighted-text::before { content: ""; position: absolute; top: 0px; left: -5px; right: -5px; bottom: 0px; background-color: #FF7F50; z-index: -1; border-radius: 3px; transform: rotate(-1deg); box-shadow: 2px 2px 0 #FF7F50, 4px 1px 0 #FF7F50, -2px 2px 0 #FF7F50, -4px 1px 0 #FF7F50, 0 4px 2px rgba(255, 127, 80, 0.4), 0 -4px 2px rgba(255, 127, 80, 0.4), 0 0 5px rgba(0, 0, 0, 0.1); } .experience-titles { display: flex; justify-content: center; gap: 30px; padding: 15px 0; } .experience-title { cursor: pointer; padding: 10px 23px; border-bottom: 2px solid transparent; border-radius: 30px; transition: all 0.3s ease; font-family: 'League Spartan', sans-serif; font-weight: 500; font-size: 20px; opacity: 0.4; } .experience-title:hover { background-color: #f0f0f0; } .experience-title:active { background-color: #e0e0e0; } .experience-title.clicked { background-color: #e0e0e0; opacity: 1; } .experience-description { margin-top: 20px; font-family: 'Open Sans', sans-serif; font-weight: normal; font-size: 16px; padding-left: 28px; opacity: 0; transition: opacity 0.25s ease-in-out; } .experience-description.fade-in { opacity: 1; } .experience-position { font-family: 'Montserrat', sans-serif; font-size: 20px; font-weight: 600; } .experience-date { font-family: 'Montserrat', sans-serif; font-size: 16px; font-weight: 600; margin-bottom: 12px; } .experience-points { font-family: 'Montserrat', sans-serif; font-size: 16px; font-weight: 400; padding-left: 14px; } .experience-points li { margin-bottom: 10px; }
原理说明
- 通过
ref获取容器和描述区域的DOM元素,动态计算实际高度 - 切换职位时,先将容器高度设为当前固定值,替换内容后再计算新内容高度并设置,触发CSS的
height过渡动画 - 配合原有淡入淡出效果,让整体切换更流畅
内容的提问来源于stack exchange,提问作者aliensarefake
相关产品推荐
相关产品推荐

