实现多个独立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
相关产品推荐
相关产品推荐

