简体   繁体   中英

How to close accordion panels when another one opens?

I have accordion widget on my static HTML/CSS/JS site.

By default all accordions are closed.

User can open any of them and those will stay opened until the user manually closes them by clicking on the accordion title.

How can I make previously opened accordions to close when users click to open another one?

I created my accordion using this template: https://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_accordion_animate

I put both HTML and JS in the HTML file:

 var acc = document.getElementsByClassName("accordion"); var i; for (i = 0; i < acc.length; i++) { acc[i].addEventListener("click", function() { this.classList.toggle("active"); var panel = this.nextElementSibling; if (panel.style.maxHeight){ panel.style.maxHeight = null; } else { panel.style.maxHeight = panel.scrollHeight + "px"; } }); } 
 .accordion { color: #fff; background-color: #00000042; cursor: pointer; padding: 10px 10px; margin-top:20px; width: 100%; border: 1px solid #00000042; text-align: left; outline: none; transition: 0.4s; } .active, .accordion:hover { background-color: #00000042; border: 1px solid #fc8e2d; } .panel { padding: 0 18px; font-size: 18px; background-color: #00000042; max-height: 0; overflow: hidden; transition: max-height 0.3s ease-out; } 
 <button class="accordion"><span class="faq__question-heading">Title1</span></button> <div class="panel"> <p style="padding:18px 0;">description1</p> </div> <button class="accordion"><span class="faq__question-heading">Title2</span></button> <div class="panel"> <p style="padding:18px 0;">description2</p> </div> <button class="accordion"><span class="faq__question-heading">Title3</span></button> <div class="panel"> <p style="padding:18px 0;">description3</p> </div> <script> </script> 

Just reset to null the others' maxHeight property inside the <script> like this:

 <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> .accordion { background-color: #eee; color: #444; cursor: pointer; padding: 18px; width: 100%; border: none; text-align: left; outline: none; font-size: 15px; transition: 0.4s; } .active, .accordion:hover { background-color: #ccc; } .panel { padding: 0 18px; background-color: white; max-height: 0; overflow: hidden; transition: max-height 0.2s ease-out; } </style> </head> <body> <h2>Animated Accordion</h2> <p>Click on the buttons to open the collapsible content.</p> <button class="accordion">Section 1</button> <div class="panel"> <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p> </div> <button class="accordion">Section 2</button> <div class="panel"> <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p> </div> <button class="accordion">Section 3</button> <div class="panel"> <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p> </div> <script> var acc = document.getElementsByClassName("accordion"); for (let i = 0; i < acc.length; i++) { acc[i].addEventListener("click", function() { this.classList.toggle("active"); for (j = 0; j < acc.length; j++) { if (j !== i) acc[j].nextElementSibling.style.maxHeight = null; } var panel = this.nextElementSibling; if (panel.style.maxHeight) { panel.style.maxHeight = null; } else { panel.style.maxHeight = panel.scrollHeight + "px"; } }); } </script> </body> </html> 

You can use one more for loop inside click event handler that will reset the height. It will also close the accordion if it is opened.

for (var j = 0; j < acc.length; j++) {
    acc[j].nextElementSibling.style.maxHeight = null;
}

See the Snippet below:

 var acc = document.getElementsByClassName("accordion"); for (let i = 0; i < acc.length; i++) { acc[i].addEventListener("click", function() { for (let j = 0; j < acc.length; j++) { acc[j].classList.remove("active"); if(j!=i){ acc[j].nextElementSibling.style.maxHeight = null; } } this.classList.add("active"); var panel = this.nextElementSibling; if (panel.style.maxHeight){ panel.style.maxHeight = null; } else { panel.style.maxHeight = panel.scrollHeight + "px"; } }); } 
 .accordion { color: #fff; background-color: #00000042; cursor: pointer; padding: 10px 10px; margin-top:20px; width: 100%; border: 1px solid #00000042; text-align: left; outline: none; transition: 0.4s; } .active, .accordion:hover { background-color: #00000042; border: 1px solid #fc8e2d; } .panel { padding: 0 18px; font-size: 18px; background-color: #00000042; max-height: 0; overflow: hidden; transition: max-height 0.3s ease-out; } 
 <button class="accordion"><span class="faq__question-heading">Title1</span></button> <div class="panel"> <p style="padding:18px 0;">description1</p> </div> <button class="accordion"><span class="faq__question-heading">Title2</span></button> <div class="panel"> <p style="padding:18px 0;">description2</p> </div> <button class="accordion"><span class="faq__question-heading">Title3</span></button> <div class="panel"> <p style="padding:18px 0;">description3</p> </div> <script> </script> 

There are already some answers given while I finished the code ;). However I will do the contribution anyway. Every time an accordion is opened you have to execute some extra code. You have to get the element which has the class .active - in your case we are going with .accordion.active . After that it is necessary remove the .active class from the found element. Finally you have to update the max-height of the following panel. You can do this with nextElementSilbling . Here is the updated JS code:

  let active = document.querySelectorAll(".accordion-div .accordion.active");
  for(let j = 0; j < active.length; j++){
    active[j].classList.remove("active");
    active[j].nextElementSibling.style.maxHeight = null; //or 0px
  }

Additionally I updated the HTML to better encapsulate the component. I wrapped a <div> around everything to better apply a selector for retrieving the element. Hope this helps. I copied your answer and updated it to generate a working example. If you want to use multiple accordions per page I would recommend to work with id= to retrieve the correct one. Otherwise all accordions would get closed which shouldn't happen intentionally.

 var acc = document.getElementsByClassName("accordion"); var i; for (i = 0; i < acc.length; i++) { acc[i].addEventListener("click", function() { var panel = this.nextElementSibling; if (panel.style.maxHeight){ panel.style.maxHeight = null; } else { let active = document.querySelectorAll(".accordion-div .accordion.active"); for(let j = 0; j < active.length; j++){ active[j].classList.remove("active"); active[j].nextElementSibling.style.maxHeight = null; } this.classList.toggle("active"); panel.style.maxHeight = panel.scrollHeight + "px"; } }); } 
 .accordion { color: #fff; background-color: #00000042; cursor: pointer; padding: 10px 10px; margin-top:20px; width: 100%; border: 1px solid #00000042; text-align: left; outline: none; transition: 0.4s; } .active, .accordion:hover { background-color: #00000042; border: 1px solid #fc8e2d; } .panel { padding: 0 18px; font-size: 18px; background-color: #00000042; max-height: 0; overflow: hidden; transition: max-height 0.3s ease-out; } 
 <div class="accordion-div"> <button class="accordion"><span class="faq__question-heading">Title1</span></button> <div class="panel"> <p style="padding:18px 0;">description1</p> </div> <button class="accordion"><span class="faq__question-heading">Title2</span></button> <div class="panel"> <p style="padding:18px 0;">description2</p> </div> <button class="accordion"><span class="faq__question-heading">Title3</span></button> <div class="panel"> <p style="padding:18px 0;">description3</p> </div> </div> 

var acc = document.getElementsByClassName("accordion");
var i;

for (i = 0; i < acc.length; i++) {
acc[i].addEventListener("click", function() {
this.classList.toggle("active");

// init: close all accordions
var allAcc = document.getElementsByClassName("accordion");
for (var e = 0; e < allAcc.length; e++) {
  var aPanel = allAcc[e].nextElementSibling;
  aPanel.style.maxHeight = null;
}

var panel = this.nextElementSibling;
if (panel.style.maxHeight){
  panel.style.maxHeight = null;
} else {
  panel.style.maxHeight = panel.scrollHeight + "px";
}
});
}

Edit: I've added 5 lines after the comment "// init: close all accordions". As the comment says these lines close all accordions. This is done by getting all elements with class "accordion". By looping through these elements the panel div which holds the accordion description is used (next sibling element to the accordion element). The height of this panel element is set to "null" - so it has no height (similiar to "invisible" or "display:none"). I have used this code because it is also used in the code of the asker to close a panel when someone clicks an accordion.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM