React组件DOM嵌套警告修复及ReadMore过渡动画实现求助
ReadMore组件问题解决方案
一、修复DOM嵌套警告 (<ul> cannot appear as a descendant of <p>)
问题原因
HTML规范中,<p>标签属于行内块级元素,仅能包含行内元素或文本,无法嵌套<ul>这类块级元素。你的ReadMore组件用<p>包裹了包含<ul>的子元素,违反DOM嵌套规则,导致控制台警告。
修复步骤
替换ReadMore中的
<p>为<div>
将ReadMore组件内的<p>标签改为<div>(通用块级容器,支持嵌套任意块级元素):const ReadMore = ({ children }) => { const [isReadMore, setIsReadMore] = useState(true); const toggleReadMore = () => { setIsReadMore(!isReadMore); }; return ( <div className="read-more-wrapper"> <div className={`content-container ${isReadMore ? 'collapsed' : 'expanded'}`}> {children} </div> <span onClick={toggleReadMore} className="read-or-hide" style={{ color: "#00ADB5", cursor: 'pointer', marginTop: '8px', display: 'inline-block' }} > {isReadMore ? "Position Activities" : " show less"} </span> </div> ); };修正
<ul>内部的非法嵌套
原代码中<ul>直接包含<p>标签,违反<ul>子元素只能是<li>的规范。将<p>改为<li>并添加样式去除列表标记:<ReadMore> <div className={`role_explanation_experience`}> <ul> <li className="section-header">Daily Activities:</li> <li>SOMETHING</li> <li>SOMETHING</li> {/* ...其他列表项 */} <li className="section-header">Achievements:</li> <li>SOMETHING</li> <li>SOMETHING</li> </ul> </div> </ReadMore>配套CSS:
.section-header { list-style: none; font-weight: 600; margin: 12px 0 8px; }
二、实现过渡动画效果
问题分析
之前动画失效的核心原因:
- 直接切换内容(
isReadMore ? text[0] : text)会导致DOM元素被替换,无法触发CSS过渡 <p>标签的高度特性不适合做过渡动画
实现方案:用max-height实现平滑过渡
通过控制内容容器的max-height属性实现展开/收起动画(height: auto无法直接过渡,max-height是可行替代方案):
添加CSS过渡样式
.content-container { max-height: 120px; /* 收起时的高度,根据初始展示内容调整 */ overflow: hidden; transition: max-height 0.3s ease-in-out; position: relative; } .content-container.expanded { max-height: 2000px; /* 足够大的值,确保容纳所有内容 */ } /* 可选:添加渐变遮罩,提示内容被截断 */ .content-container.collapsed::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 40px; /* 替换为你的页面背景色 */ background: linear-gradient(to bottom, rgba(255,255,255,0), rgba(17, 24, 39, 1)); }调整ReadMore组件逻辑
不再直接替换内容,而是通过max-height控制显示/隐藏,确保DOM元素始终存在以触发过渡:const ReadMore = ({ children }) => { const [isReadMore, setIsReadMore] = useState(true); const toggleReadMore = () => { setIsReadMore(!isReadMore); }; return ( <div className="read-more-wrapper"> <div className={`content-container ${isReadMore ? 'collapsed' : 'expanded'}`}> {children} </div> <span onClick={toggleReadMore} className="read-or-hide" style={{ color: "#00ADB5", cursor: 'pointer', marginTop: '8px', display: 'inline-block' }} > {isReadMore ? "Position Activities" : " show less"} </span> </div> ); };
最终完整代码示例
ReadMore组件
import { useState } from 'react'; const ReadMore = ({ children }) => { const [isReadMore, setIsReadMore] = useState(true); const toggleReadMore = () => { setIsReadMore(!isReadMore); }; return ( <div className="read-more-wrapper"> <div className={`content-container ${isReadMore ? 'collapsed' : 'expanded'}`}> {children} </div> <span onClick={toggleReadMore} className="read-or-hide" style={{ color: "#00ADB5", cursor: 'pointer', marginTop: '8px', display: 'inline-block' }} > {isReadMore ? "Position Activities" : " show less"} </span> </div> ); }; export default ReadMore;
Experience组件
import ReadMore from './ReadMore'; import Nymbus from './path-to-nymbus-image'; export default function Experience() { return ( <> <div className='main_div_experience_section'> <div className='experience_h1'> <span className='text-6xl font-bold text-white'> Experience </span> </div> <div className='parent_section'> <div className='rectangle'> <div className='grid'> <div className='col1'> <div className='years_experience'><p>Sep 2023 - Present</p></div> </div> <div className='col2'> <div className='text_company'> <div className='grouper_name_and_logo'> <div className='img_div_experience'> <a href="home"> <img src={Nymbus} alt="Nymbus Logo" className='company_img_experience' /> </a> </div> <h1 className='role_experience text-gray-400 text-xl font-normal'> Full Stack Developer - asdfasdf </h1> </div> <ReadMore> <div className={`role_explanation_experience`}> <ul> <li className="section-header">Daily Activities:</li> <li>SOMETHING</li> <li>SOMETHING</li> <li>SOMETHING</li> <li>SOMETHING</li> <li>SOMETHING</li> <li>SOMETHING</li> <li>SOMETHING</li> <li>SOMETHING</li> <li className="section-header">Achievements:</li> <li>SOMETHING</li> <li>SOMETHING</li> </ul> </div> </ReadMore> </div> </div> </div> </div> </div> </div> </> ) }
配套CSS
.read-more-wrapper { margin-top: 12px; } .content-container { max-height: 120px; overflow: hidden; transition: max-height 0.3s ease-in-out; position: relative; } .content-container.expanded { max-height: 2000px; } .content-container.collapsed::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 40px; background: linear-gradient(to bottom, rgba(255,255,255,0), rgba(17, 24, 39, 1)); } .section-header { list-style: none; font-weight: 600; margin: 12px 0 8px; color: #fff; }
内容的提问来源于stack exchange,提问作者David Torres
相关产品推荐
相关产品推荐

