You need to enable JavaScript to run this app. 使用ReactJS实现带动效的切换栏内容区块切换功能咨询
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.08 00:15:04