使用ReactJS实现带动效的切换栏内容区块切换功能咨询
React Toggle切换模块实现方案
前置问题修复
- CSS中active类选择器书写错误:
#toggle-header li .active会选中li内部的active子元素,实际active是li自身的类,需改为#toggle-header li.active - 无效的
:visited伪类:li不是可访问链接,该样式不会生效,需删除
核心实现逻辑
- 用类组件state存储当前激活tab的标识,默认值为
'venue'(对应Venue项) - 预配置所有tab的元数据(名称、对应内容数据),渲染时遍历生成tab项,避免硬编码重复结构
- 单独渲染指示器元素,通过ref获取激活tab的位置、宽度,同步更新指示器的样式属性,配合CSS过渡实现平滑动效
- 内容区根据激活tab的标识渲染对应数据,通过CSS类名切换实现内容淡入淡出的过渡效果
改造后的完整代码
组件代码
import React, { Component } from 'react' // 预配置tab数据 const TAB_CONFIG = [ { id: 'venue', label: 'Venue', content: { addressTitle: 'Brighton Waterfront Hotel, Brighton, London', addressDetail: '1Hd- 50, 010 Avenue, NY 90001<br/>United States', infoColumns: [ { title: 'Tickets info', items: [['Name', 'Ronaldo König'], ['Phone', '009-215-5595'], ['Email', 'info@example.com']] }, { title: 'Programme Details', items: [['Name', 'Ronaldo König'], ['Phone', '009-215-5595'], ['Email', 'info@example.com']] } ] } }, { id: 'time', label: 'Time', content: { addressTitle: 'Event Opening Hours', addressDetail: 'Day 1: 09:00 - 18:00<br/>Day 2: 10:00 - 17:00', infoColumns: [ { title: 'Check-in Time', items: [['Early Access', '08:30'], ['Standard', '09:00']] }, { title: 'Closing Time', items: [['Last Entry', '17:00'], ['Venue Close', '18:00']] } ] } }, { id: 'transport', label: 'How to get there', content: { addressTitle: 'Transport Guidance', addressDetail: 'Subway: Line 2 Brighton Station<br/>Bus: Route 12 / 45 to Event Hall Stop', infoColumns: [ { title: 'Parking Info', items: [['Public Lot', 'Opposite Venue'], ['Free Access', 'Event Badge Holder']] }, { title: 'Shuttle Bus', items: [['Schedule', 'Every 30 mins'], ['Pickup Point', 'Main Subway Exit']] } ] } } ] class Directions extends Component { constructor(props) { super(props) this.state = { activeTabId: 'venue', // 默认激活Venue项 indicatorLeft: 0, indicatorWidth: 0 } // 存储tab的DOM引用 this.tabRefs = TAB_CONFIG.reduce((acc, tab) => ({ ...acc, [tab.id]: React.createRef() }), {}) } componentDidMount() { // 初始渲染时设置默认指示器位置 this.updateIndicator(this.state.activeTabId) } // 切换tab的处理函数 handleTabChange = (tabId) => { this.setState({ activeTabId: tabId }) this.updateIndicator(tabId) } // 更新指示器位置和宽度 updateIndicator = (tabId) => { const activeTabEl = this.tabRefs[tabId].current if (!activeTabEl) return this.setState({ indicatorLeft: activeTabEl.offsetLeft, indicatorWidth: activeTabEl.offsetWidth }) } render() { const { activeTabId, indicatorLeft, indicatorWidth } = this.state // 获取当前激活tab的内容 const activeTab = TAB_CONFIG.find(tab => tab.id === activeTabId) return ( <section id="Directions-section"> <div id="directions-div"> <div id="directions-text"> <div id="d-img"> <img style={{ position: "relative", top: "60px" }} src="/placeholder-direction.png" alt="Direction decoration" /> </div> <div id="d-t"> <div id="htgt"> <h1> <span>REACH US</span> Get Direction to the <br />Event Hall<br/> <img src="/placeholder-title.png" alt="Title decoration"/> </h1> </div> <div id="toggle"> <div id="toggle-header"> <ul> {TAB_CONFIG.map(tab => ( <li key={tab.id} ref={this.tabRefs[tab.id]} className={activeTabId === tab.id ? 'active' : ''} onClick={() => this.handleTabChange(tab.id)} > {tab.label} </li> ))} {/* 粉色指示器 */} <div id="toggle-indicator" style={{ left: `${indicatorLeft}px`, width: `${indicatorWidth}px` }} ></div> </ul> </div> {/* 内容区,加key触发切换动画 */} <div id="toggle-content" key={activeTabId}> <div id="toggle-address"> <h2 dangerouslySetInnerHTML={{__html: activeTab.content.addressTitle}}></h2> <span dangerouslySetInnerHTML={{__html: activeTab.content.addressDetail}}></span> </div> <div id="toggle-info"> {activeTab.content.infoColumns.map((column, idx) => ( <div key={idx} className="info-column"> <h2>{column.title}</h2> {column.items.map(([label, value], itemIdx) => ( <h2 key={itemIdx}>{label}: <span>{value}</span></h2> ))} </div> ))} </div> </div> </div> </div> </div> </div> </section> ) } } export default Directions
配套CSS代码
#Directions-section { position: relative; top: 320px; width: 100%; height: 600px; } #directions-div { position: absolute; top: 0px; width: 100%; height: 100%; display: flex; flex-direction: row; } #directions-text { flex: 1; display: flex; flex-direction: row; } #d-img { flex: 1; } #d-t { flex: 5; width: 100%; height: 100%; display: flex; flex-direction: column; padding: 20px; } #htgt { flex: 1; width: 100%; } #htgt h1 { font-size: 36px; font-weight: 800; color: #3b1d82; font-family: 'Raleway', sans-serif; line-height: 1.2; } #htgt span { display: block; font-size: 14px; font-family: "Roboto", sans-serif; font-weight: 400; color: #888888; } #toggle { flex: 2; width: 100%; display: flex; flex-direction: column; } #toggle-header { flex: 1; width: 100%; } #toggle-header ul { position: relative; /* 为指示器做定位父级 */ padding: 0; margin: 0; } #toggle-header ul li { display: inline-block; list-style: none; margin-right: 50px; padding: 8px 0; /* 调整内边距优化点击区域 */ color: #888888; font-family: 'Roboto', sans-serif; font-size: 16px; font-weight: 700; cursor: pointer; /* 移除原li自带的边框,统一由指示器实现底部边框 */ border-bottom: none; transition: color 0.3s ease; } /* 修复后的active选择器 */ #toggle-header li.active { color: #e7015e; } /* 粉色指示器样式 */ #toggle-indicator { position: absolute; bottom: 0; height: 2px; background-color: #e7015e; /* 指示器过渡动效 */ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } #toggle-content { /* 内容切换过渡效果 */ animation: fadeIn 0.3s ease forwards; } /* 内容淡入动画关键帧 */ @keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } #toggle-address { flex: 1; width: 100%; margin-top: 20px; } #toggle-address h2 { font-size: 20px; color: #1c1c24; font-family: 'Roboto', sans-serif; font-weight: 700; margin: 0 0 10px 0; } #toggle-address span { display: block; font-size: 16px; color: #888888; font-family: 'Roboto', sans-serif; font-weight: normal; } #toggle-info { flex: 3; width: 100%; display: flex; flex-direction: row; margin-top: 20px; } .info-column { flex: 1; width: 100%; } .info-column h2 { font-family: 'Roboto', sans-serif; font-size: 16px; color: #222222; margin: 0 0 12px 0; } .info-column span { color: #888888; }
效果说明
- 点击任意tab时,会同步更新激活状态、指示器位置,指示器会以平滑过渡效果滑动到对应tab下方
- 内容区会触发淡入上浮的过渡动画,不会出现生硬切换
- 所有tab内容配置集中在
TAB_CONFIG中,后续修改内容只需修改该配置项,无需改动组件逻辑
内容的提问来源于stack exchange,提问作者Uzair_Abdullah
相关产品推荐
相关产品推荐

