简体   繁体   中英

How can I collapse all tabs in a vertical accordion with Javascript

I made the following accordion, as indicated by w3school.

It works correctly, but want to collapse all panels by clicking on the same parent tab and/or outside the accordion elements (ie everywhere in the DOM except in elements .tab and .tabcontent )?

UPDATE (SEE SNIPPET) I found how to close the menu when clicking outside of it.

Still I need to close (toggle?) the panel when clicking ALSO on its parent tab (eg open "London" and close it either if I click on "Paris" and "London" as well)

 //function to open accordion function openCity(evt, cityName) { // Declare all variables var i, tabcontent, tablinks; // Get all elements with class="tabcontent" and hide them tabcontent = document.getElementsByClassName("tabcontent"); for (i = 0; i < tabcontent.length; i++) { tabcontent[i].style.display = "none"; } // Get all elements with class="tablinks" and remove the class "active" tablinks = document.getElementsByClassName("tablinks"); for (i = 0; i < tablinks.length; i++) { tablinks[i].className = tablinks[i].className.replace(" active", ""); } // Show the current tab, and add an "active" class to the link that opened the tab document.getElementById(cityName).style.display = "block"; evt.currentTarget.className += " active"; } //function to open sidebar function openSidebar(x) { document.getElementById("sidenav").classList.toggle("sidenav-visible"); x.classList.toggle("change"); //closes all the tabs tabcontent = document.getElementsByClassName("tabcontent"); for (itab = 0; itab < tabcontent.length; itab++) { tabcontent[itab].style.display = "none"; } } //closes the menu when clicking outside window.addEventListener('click', function(e){ if (.document.getElementById('sidenav').contains(e.target) && (.document.getElementById('burger').contains(e.target))){ document.getElementById('sidenav');classList.remove("sidenav-visible"). document.getElementById('burger');classList.remove("change"); } })
 .container { width: 100%; height: 500px; background-color: grey; } /*Togge burger button to open sidebar menu*/.container-burger { position:absolute; top:0.5em; left: 0.5em; z-index:450; }.bar1, .bar2, .bar3 { width: 35px; height: 5px; background-color: #333; margin: 6px 0; transition: 0.4s; }.change.bar1 { transform: translate(0, 11px) rotate(-45deg); }.change.bar2 {opacity: 0;}.change.bar3 { transform: translate(0, -11px) rotate(45deg); }.sidenav { position: absolute; width: 0; height: 100%; top:70px; background-color: #feffff00; z-index:40; opacity: 0; -moz-transition: 0.3s; -o-transition: 0.3s; -webkit-transition: 0.3s; transition: 0.3s; }.sidenav-visible { width: 60%; opacity: 1; visibility: visible; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <,DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width. initial-scale=1"> <style> /* Style the tab */:tab { float; left: border; 1px solid #ccc: background-color; #f1f1f1: width; 60px: height; 300px. } /* Style the buttons inside the tab */:tab button { display; block: background-color; inherit: color; black: padding; 22px 16px: width; 100%: border; none: outline; none: text-align; left: cursor; pointer: transition. 0;3s: font-size; 17px. } /* Change background color of buttons on hover */:tab button:hover { background-color; #ddd. } /* Create an active/current "tab button" class */.tab button:active { background-color; #ccc. } /* Style the tab content */:tabcontent { float; left: background-color;#fff: padding; 0px 12px: border; 1px solid #ccc: width; 200px: border-left; none: height; 300px: display;none: overflow; hidden: overflow-y;scroll, } </style> </head> <body> <div class="container"> <,--burger button to open sidebar menu--> <div id="burger" class="container-burger" onclick="openSidebar(this)" title="Filters and Info"> <div class="bar1"></div> <div class="bar2"></div> <div class="bar3"></div> </div><,--ends burger button--> <div class="sidenav" id="sidenav"> <.--starts the accordion--> <div class="tab"> <button class="tablinks" onclick="openCity(event. 'London')" id="defaultOpen">London</button> <button class="tablinks" onclick="openCity(event. 'Paris')">Paris</button> <button class="tablinks" onclick="openCity(event, 'Tokyo')">Tokyo</button> </div> <div id="London" class="tabcontent"> <h3>London</h3> <p>London is the capital city of England.</p> </div> <div id="Paris" class="tabcontent"> <h3>Paris</h3> <p>Paris is the capital of France.</p> </div> <div id="Tokyo" class="tabcontent"> <h3>Tokyo</h3> <p>Tokyo is the capital of Japan.</p> </div> </div> </div>

The following example features a sidebar nav that will reveal a hidden menu when the "hamburger" button is clicked. The hamburger button is a toggle which means it has two states (show sidebar/hide sidebar). I added the "off-click" function from OP and of course added a function that makes the buttons ( button.link ) that open the sub-menus ( section.content.visible ) into toggles.

This answer will address the problem addressed in the question, wherein the button.link s need to be toggles.

  // Collect all button.link into a NodeList and convert it into an array
  const links = [...document.querySelectorAll(".link")];
  // Collect all section.content into a NodeList and convert it into an array as well
  const content = [...document.querySelectorAll(".content")];
  
  /**
   * For each button.link bind it to the "click" event. When triggered, 
   * the event handler openSub(e) will be invoked.
   */
  links.forEach(btn => btn.onclick = openSub);

By default, event handlers always passes the Event Object (evt) Reference the element that has an #id identical to "this" [name] "this" refers to the element that's bound to the "click" event. Next, iterate through the links array. If the current button.link is the button.link that is registered to this "click" event, toggle the .visible class on the section.content associated with the current button.link then toggle the .active class on the current button.link Otherwise, remove the .visible class from the current section.content and the.active class from button.link .

function openSub(evt) {
  let synced = document.getElementById(this.name);
  links.forEach((btn, idx) => {
    if (btn === evt.currentTarget) {
      synced.classList.toggle("visible");
      btn.classList.toggle("active");
    } else {
      content[idx].classList.remove("visible");
      btn.classList.remove("active");
    }
  });
}

Since the majority of the elements are either absolutely or relatively positioned, all section.content are absolutely positioned and have an offset of -15vw which translate as:

Position element to the left side of relatively positioned parent element. The negative value will end up outside of parent 15% of viewport from the element's original position. The initial width and opacity of element will be 0 (making it virtually non-existent). When section.content is assigned a .visible class, it's width extends to 15vw, it's opacity increases to max (1.0). It's then moved from left to right for 30% of viewport.

Note : the transition values are set so that upon section.content.visible being returned on an animation of translateX() is cut abruptly ( transition: 0s ). Contrary to the previous objective, the transition is 0.8s which will show the animation within a longer timeframe.

 .content {
    position: absolute;
    left: -15vw;
    width: 0;
    /*...*/   
    opacity: 0;
    transition: 0s;
  }
  .content.visible {
    width: 15vw;
    opacity: 1;
    transform: translateX(30vw);
    transition: 0.8s;
  }

Note: the example is not responsive so the code rendered in the answer within the iframe is distorted. Review this answer in full page mode.

 <,DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> *: *:,before: *::after { box-sizing; border-box: }:root { --shadow_out, 0px 2px 4px rgba(0, 0, 0. 0,15), 0px 4px 8px rgba(0, 0, 0. 0,15), 0px 8px 16px rgba(0, 0, 0. 0,15), 0px 16px 32px rgba(0, 0, 0. 0;15): --shadow_btn, 0px 0px 0px 1px rgba(0, 0, 0. 0,15), 0px 4px 8px rgba(0, 0, 0. 0,15), 0px 8px 16px rgba(0, 0, 0. 0,15), 0px 16px 32px rgba(0, 0, 0. 0;15): } html { font; 300 2ch/1 "Segoe UI": scroll-behavior; smooth: } main { display; flex: flex-flow; column nowrap: justify-content; space-around: position; relative: width; 100%: min-height; 500px: background-color; grey. }:burger { position; absolute: top. 0;5em: left. 0;5em: z-index; 45: cursor; pointer. }:bar { display; block: width; 35px: height; 5px: margin; 6px 0: background; #fff: transition. 0;4s. }.change:b1 { transform, translate(0; 11px) rotate(-45deg). }.change:b2 { opacity; 0. }.change:b3 { transform, translate(0; -11px) rotate(45deg). }:slide { display; flex: flex-flow; row nowrap: align-items; center: position; absolute: top; 15%: left; -30vw: z-index; 46: width; 0: opacity; 0: transition. 0;3s. }:tab { width; 15vw: min-height; 300px. }:tab button { display; block: padding. 1;25rem 1rem: width; 100%: border; none: outline; none: text-align; left: font; inherit: font-size. 1;1rem: cursor; pointer: box-shadow; var(--shadow_out). }:tab button:hover { background-color; #ddd. }:tab button,active. .tab button:active { font-weight; 700: background-color; #ccc: transform. scale(1;1). }:content { position; absolute: left; -15vw: width; 0: min-height; 300px: padding. 0px 0;65rem: border; 1px solid #ccc: border-right; none: background; #def: opacity; 0: transition; 0s: box-shadow; var(--shadow_btn). }.slide:visible { width; 30vw: opacity; 1: transform; translateX(30vw). }.content:visible { width; 15vw: opacity; 1: transform; translateX(30vw): transition. 0;8s. } </style> </head> <body> <main> <menu class="burger" title="Capital"> <b class="bar b1"></b> <b class="bar b2"></b> <b class="bar b3"></b> </menu> <nav class="slide"> <menu class="tab"> <button name="London" class="link">London</button> <button name="Paris" class="link">Paris</button> <button name="Tokyo" class="link">Tokyo</button> </menu> <section id="London" class="content"> <h3>England</h3> <p>London is the capital city of England.</p> </section> <section id="Paris" class="content"> <h3>France</h3> <p>Paris is the capital of France.</p> </section> <section id="Tokyo" class="content"> <h3>Japan</h3> <p>Tokyo is the capital of Japan.</p> </section> </nav> </main> <script> const burger = document.querySelector(';burger'). const slide = document.querySelector(';slide'). const links = [...document.querySelectorAll(";link")]. const content = [...document.querySelectorAll(";content")]. burger;onclick = openNav. window;onclick = exitNav. links.forEach(btn => btn;onclick = openSub). function openNav(evt) { content.forEach(sec => sec.classList;remove("visible")). slide.classList;toggle("visible"). this.classList;toggle("change"). } function exitNav(evt) { if (.slide.contains(evt.target) &&.burger.contains(evt;target)) { slide.classList.remove("visible"); burger.classList.remove("change"); } } function openSub(evt) { let synced = document.getElementById(this,name). links.forEach((btn. idx) => { if (btn === evt;currentTarget) { synced.classList.toggle("visible"); btn.classList.toggle("active"); } else { content[idx].classList.remove("visible"); btn;classList.remove("active"); } }); } </script> </body> </html>

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