You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

实现多个独立Accordion组件:默认展开各组件首面板

独立Accordion组件实现方案

问题概述

使用W3Schools的Accordion模块时,页面中多个Accordion组件会出现联动问题:

  • 仅全局第一个面板默认展开,而非每个Accordion组的首个面板都展开
  • 操作任意一个组的面板时,其他组的面板会被强制关闭,无法独立运作

以下是结合ACF repeater的演示代码:

原HTML+PHP代码

<div class="row">
  <div class="col-5">
    <h3>Title</h3>
  </div>
  <div class="col-5">
    <button class="accordion"><h4><?php the_sub_field('titel'); ?></h4><br /></button>
    <div class="panel">
      <p>
        <?php the_sub_field('content'); ?>
      </p>
    </div>
    <button class="accordion"><h4><?php the_sub_field('titel'); ?></h4><br /></button>
    <div class="panel">
      <p>
        <?php the_sub_field('content'); ?>
      </p>
    </div>
  </div>
</div>
<div class="row">
  <div class="col-5">
    <h3>Title</h3>
  </div>
  <div class="col-5">
    <button class="accordion"><h4><?php the_sub_field('titel'); ?></h4><br /></button>
    <div class="panel">
      <p>
        <?php the_sub_field('content'); ?>
      </p>
    </div>
    <button class="accordion"><h4><?php the_sub_field('titel'); ?></h4><br /></button>
    <div class="panel">
      <p>
        <?php the_sub_field('content'); ?>
      </p>
    </div>
  </div>
</div>

原JavaScript代码

// W3C's JS Code
var acc = document.getElementsByClassName("accordion");
var i;

// Open the first accordion
var firstAccordion = acc[0];
var firstPanel = firstAccordion.nextElementSibling;
firstAccordion.classList.add("active");
firstPanel.style.maxHeight = firstPanel.scrollHeight + "px";

// Add onclick listener to every accordion element
for (i = 0; i < acc.length; i++) {
  acc[i].onclick = function() {
    // For toggling purposes detect if the clicked section is already "active"
    var isActive = this.classList.contains("active");

    // Close all accordions
    var allAccordions = document.getElementsByClassName("accordion");
    for (j = 0; j < allAccordions.length; j++) {
      // Remove active class from section header
      allAccordions[j].classList.remove("active");

      // Remove the max-height class from the panel to close it
      var panel = allAccordions[j].nextElementSibling;
      var maxHeightValue = getStyle(panel, "maxHeight");

      if (maxHeightValue !== "0px") {
        panel.style.maxHeight = null;
      }
    }

    // Toggle the clicked section using a ternary operator
    isActive ? this.classList.remove("active") : this.classList.add("active");

    // Toggle the panel element
    var panel = this.nextElementSibling;
    var maxHeightValue = getStyle(panel, "maxHeight");

    if (maxHeightValue !== "0px") {
      panel.style.maxHeight = null;
    } else {
      panel.style.maxHeight = panel.scrollHeight + "px";
    }
  };
}

// Cross-browser way to get the computed height of a certain element.
function getStyle(el, styleProp) {
  var value, defaultView = (el.ownerDocument || document).defaultView;
  if (defaultView && defaultView.getComputedStyle) {
    styleProp = styleProp.replace(/([A-Z])/g, "-$1").toLowerCase();
    return defaultView.getComputedStyle(el, null).getPropertyValue(styleProp);
  } else if (el.currentStyle) {
    styleProp = styleProp.replace(/\-(\w)/g, function(str, letter) {
      return letter.toUpperCase();
    });
    value = el.currentStyle[styleProp];
    if (/^\d+(em|pt|%|ex)?$/i.test(value)) {
      return (function(value) {
        var oldLeft = el.style.left,
          oldRsLeft = el.runtimeStyle.left;
        el.runtimeStyle.left = el.currentStyle.left;
        el.style.left = value || 0;
        value = el.style.pixelLeft + "px";
        el.style.left = oldLeft;
        el.runtimeStyle.left = oldRsLeft;
        return value;
      })(value);
    }
    return value;
  }
}

原CSS代码

button.accordion {
  /* background-color: #eee; */
  color: #444;
  cursor: pointer;
  padding: 0px;
  width: 100%;
  border: none;
  text-align: left;
  outline: none;
  font-size: 15px;
  transition: 0.4s;
  background: none;
}

button.accordion.active,
button.accordion:hover {
  /* background-color: #ddd; */
}

button.accordion:before {
  content: '\002B';
  color: #151515;
  font-size: 2rem;
  font-weight: normal;
  /* float: right; */
  /* margin-left: 5px; */
  margin-right: 10px;
}

button.accordion.active:before {
  content: "\2212";
}

div.panel {
  padding: 10px 0px 0px 0px;
  /* background-color: white; */
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.2s ease-out;
}

解决方案

1. 调整HTML结构,添加分组容器

给每个包含Accordion按钮和面板的.col-5元素添加accordion-group类,将每个Accordion组独立包裹:

<div class="row">
  <div class="col-5">
    <h3>Title</h3>
  </div>
  <div class="col-5 accordion-group"> <!-- 添加分组类 -->
    <button class="accordion"><h4><?php the_sub_field('titel'); ?></h4><br /></button>
    <div class="panel">
      <p>
        <?php the_sub_field('content'); ?>
      </p>
    </div>
    <button class="accordion"><h4><?php the_sub_field('titel'); ?></h4><br /></button>
    <div class="panel">
      <p>
        <?php the_sub_field('content'); ?>
      </p>
    </div>
  </div>
</div>
<div class="row">
  <div class="col-5">
    <h3>Title</h3>
  </div>
  <div class="col-5 accordion-group"> <!-- 添加分组类 -->
    <button class="accordion"><h4><?php the_sub_field('titel'); ?></h4><br /></button>
    <div class="panel">
      <p>
        <?php the_sub_field('content'); ?>
      </p>
    </div>
    <button class="accordion"><h4><?php the_sub_field('titel'); ?></h4><br /></button>
    <div class="panel">
      <p>
        <?php the_sub_field('content'); ?>
      </p>
    </div>
  </div>
</div>

2. 修改JavaScript,实现组内独立控制

将原有的全局操作改为按组遍历,仅在当前组内处理展开/收起逻辑:

// 获取所有Accordion分组
const accordionGroups = document.querySelectorAll('.accordion-group');

// 遍历每个分组
accordionGroups.forEach(group => {
  // 获取当前组内的所有Accordion按钮
  const accs = group.querySelectorAll('.accordion');
  
  // 默认展开当前组的第一个面板
  if (accs.length > 0) {
    const firstAccordion = accs[0];
    const firstPanel = firstAccordion.nextElementSibling;
    firstAccordion.classList.add('active');
    firstPanel.style.maxHeight = firstPanel.scrollHeight + 'px';
  }
  
  // 给当前组内的每个按钮绑定点击事件
  accs.forEach(acc => {
    acc.onclick = function() {
      const isActive = this.classList.contains('active');
      
      // 仅关闭当前组内的所有面板
      group.querySelectorAll('.accordion').forEach(item => {
        item.classList.remove('active');
        const panel = item.nextElementSibling;
        const maxHeightValue = getStyle(panel, 'maxHeight');
        if (maxHeightValue !== '0px') {
          panel.style.maxHeight = null;
        }
      });
      
      // 切换当前按钮的激活状态
      isActive ? this.classList.remove('active') : this.classList.add('active');
      
      // 切换当前面板的展开状态
      const panel = this.nextElementSibling;
      const maxHeightValue = getStyle(panel, 'maxHeight');
      if (maxHeightValue !== '0px') {
        panel.style.maxHeight = null;
      } else {
        panel.style.maxHeight = panel.scrollHeight + 'px';
      }
    };
  });
});

// 保留原有的getStyle工具函数
function getStyle(el, styleProp) {
  var value, defaultView = (el.ownerDocument || document).defaultView;
  if (defaultView && defaultView.getComputedStyle) {
    styleProp = styleProp.replace(/([A-Z])/g, "-$1").toLowerCase();
    return defaultView.getComputedStyle(el, null).getPropertyValue(styleProp);
  } else if (el.currentStyle) {
    styleProp = styleProp.replace(/\-(\w)/g, function(str, letter) {
      return letter.toUpperCase();
    });
    value = el.currentStyle[styleProp];
    if (/^\d+(em|pt|%|ex)?$/i.test(value)) {
      return (function(value) {
        var oldLeft = el.style.left,
          oldRsLeft = el.runtimeStyle.left;
        el.runtimeStyle.left = el.currentStyle.left;
        el.style.left = value || 0;
        value = el.style.pixelLeft + "px";
        el.style.left = oldLeft;
        el.runtimeStyle.left = oldRsLeft;
        return value;
      })(value);
    }
    return value;
  }
}

3. CSS代码无需修改

原有的CSS样式可以直接复用,无需调整。


核心逻辑说明

  • 分组隔离:通过accordion-group类将每个Accordion组的DOM元素独立划分,避免全局操作影响其他组
  • 组内默认展开:遍历每个组,单独处理首个面板的默认展开逻辑,确保每个组的第一个面板都处于打开状态
  • 组内事件控制:点击按钮时仅操作当前组内的面板,完全隔离不同组之间的交互

内容的提问来源于stack exchange,提问作者Jelle

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 19:32:31