简体   繁体   中英

How to toggle display style using vanilla JS on multiple elements

I wasn't sure how to write that question logically, I pretty much figured the togglw out, but I struggle with something else. When I click on one element and open it, I want to click on another one to open and the previously opened to close. It doesn't work, however. Whenever I click on another element, the opened one just closes without opening the other. Basically, I want to close the element only when I click on that specific element. Or when I click on another one (which kinda works) but in this case also want the other to open, if that makes sense. I am stuck at this point.

Thanks for any help.

Here is my codepen: https://codepen.io/danosvk/pen/JjLEGMK

Here is my code:

 const questions = document.querySelectorAll("section"); const answers = document.querySelectorAll(".answer"); toggle = false function open() { for (let i = 0; i < answers.length; i++) { answers[i].style.display = "none"; } toggle = !toggle this.lastElementChild.style.display = toggle ? "block" : "none" } questions.forEach((question) => question.addEventListener("click", open));
 .card { width: 20.4375rem; background-color: #ffffff; border-radius: 1.4375rem; margin: auto; padding: 132px 24px 48px; text-align: center; transform: translateY(-125px); } section { display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #e8e8ea; flex-wrap: wrap; } p { color: var(--main-text-color); font-size: 0.75rem; } .answer { flex-basis: 100%; text-align: left; display: none; }
 <div class="card"> <h1 class="no">faq</h1> <section> <p class="question">How many team members can i invite?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> You can invite up to 2 additional users on the Free plan. There is no limit on team members for the Premium plan. </p> </section> <section> <p class="question">What is the maximum file upload size?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> No more than 2GB. All files in your account must fit your allotted storage space. </p> </section> <section> <p class="question">How do I reset my password?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> Click “Forgot password” from the login page or “Change password” from your profile page. A reset link will be emailed to you. </p> </section> <section> <p class="question">Can I cancel my subscription?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> Yes! Send us a message and we'll process your request no questions asked. </p> </section> <section> <p class="question">Do you provide additional support?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> Chat and email support is available 24/7. Phone lines are open during normal business hours. </p> </section>

Using toggle won't work as it is always false when an answer is showing.

Instead, save the style of the clicked option before wiping all the answers, and then use it to determine whether or not to show the answer.

If this.lastElementChild.style.display is '' (ie on the first time the menu is clicked), it is set to none .

 const questions = document.querySelectorAll("section"); const answers = document.querySelectorAll(".answer"); function open() { currentDisplay=this.lastElementChild.style.display||'none'; for (let i = 0; i < answers.length; i++) { answers[i].style.display = "none"; } if (currentDisplay=='none') this.lastElementChild.style.display = "block" } questions.forEach((question) => question.addEventListener("click", open));
 .card { width: 20.4375rem; background-color: #ffffff; border-radius: 1.4375rem; margin: auto; padding: 132px 24px 48px; text-align: center; transform: translateY(-125px); } section { display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #e8e8ea; flex-wrap: wrap; } p { color: var(--main-text-color); font-size: 0.75rem; } .answer { flex-basis: 100%; text-align: left; display: none; }
 <div class="card"> <h1 class="no">faq</h1> <section> <p class="question">How many team members can i invite?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> You can invite up to 2 additional users on the Free plan. There is no limit on team members for the Premium plan. </p> </section> <section> <p class="question">What is the maximum file upload size?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> No more than 2GB. All files in your account must fit your allotted storage space. </p> </section> <section> <p class="question">How do I reset my password?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> Click “Forgot password” from the login page or “Change password” from your profile page. A reset link will be emailed to you. </p> </section> <section> <p class="question">Can I cancel my subscription?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> Yes! Send us a message and we'll process your request no questions asked. </p> </section> <section> <p class="question">Do you provide additional support?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> Chat and email support is available 24/7. Phone lines are open during normal business hours. </p> </section>

I would do this by using this CSS rule to show the answers rather than changing their style inline:

.expanded .answer {
    display: block;
}

...and then using event delegation on the .card element that contains the FAQ to catch clicks anywhere within it; if the click passed through a section , do this:

  • If the section has the expanded class, remove it to un-expand the answer.
  • If the section doesn't have the expanded class, remove it from any other section that has it, then add it to the section that was clicked.

Here's the JavaScript part:

const card = document.querySelector(".card");
card.addEventListener("click", (event) => {
    const section = event.target.closest("section");
    if (section) {
        if (section.classList.contains("expanded")) {
            // This is the expanded one, un-expand it
            section.classList.remove("expanded");
        } else {
            // This isn't the expanded one, un-expand the expanded
            // one (if any) and expand this one
            document.querySelector("section.expanded")?.classList.remove("expanded");
            section.classList.add("expanded");
        }
    }
});

Notice the optional chaining on the querySelector looking for an expanded section; it's there in case there is no expanded section yet.

Live Example:

 const card = document.querySelector(".card"); card.addEventListener("click", (event) => { const section = event.target.closest("section"); if (section) { if (section.classList.contains("expanded")) { // This is the expanded one, un-expand it section.classList.remove("expanded"); } else { // This isn't the expanded one, un-expand the expanded // one (if any) and expand this one document.querySelector("section.expanded")?.classList.remove("expanded"); section.classList.add("expanded"); } } });
 .card { width: 20.4375rem; background-color: #ffffff; border-radius: 1.4375rem; margin: auto; padding: 132px 24px 48px; text-align: center; transform: translateY(-125px); } section { display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #e8e8ea; flex-wrap: wrap; } p { color: var(--main-text-color); font-size: 0.75rem; } .answer { flex-basis: 100%; text-align: left; display: none; } .expanded .answer { display: block; }
 <div class="card"> <h1 class="no">faq</h1> <section> <p class="question">How many team members can i invite?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> You can invite up to 2 additional users on the Free plan. There is no limit on team members for the Premium plan. </p> </section> <section> <p class="question">What is the maximum file upload size?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> No more than 2GB. All files in your account must fit your allotted storage space. </p> </section> <section> <p class="question">How do I reset my password?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> Click “Forgot password” from the login page or “Change password” from your profile page. A reset link will be emailed to you. </p> </section> <section> <p class="question">Can I cancel my subscription?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> Yes! Send us a message and we'll process your request no questions asked. </p> </section> <section> <p class="question">Do you provide additional support?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> Chat and email support is available 24/7. Phone lines are open during normal business hours. </p> </section> </div>

It might be worth adding a class to the section and using that in the selectors, in case you have other parts of the page using section , like this:

 const card = document.querySelector(".card"); card.addEventListener("click", (event) => { const section = event.target.closest(".faq"); if (section) { if (section.classList.contains("expanded")) { // This is the expanded one, un-expand it section.classList.remove("expanded"); } else { // This isn't the expanded one, un-expand the expanded // one (if any) and expand this one document.querySelector(".faq.expanded")?.classList.remove("expanded"); section.classList.add("expanded"); } } });
 .card { width: 20.4375rem; background-color: #ffffff; border-radius: 1.4375rem; margin: auto; padding: 132px 24px 48px; text-align: center; transform: translateY(-125px); } section { display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #e8e8ea; flex-wrap: wrap; } p { color: var(--main-text-color); font-size: 0.75rem; } .answer { flex-basis: 100%; text-align: left; display: none; } .expanded .answer { display: block; }
 <div class="card"> <h1 class="no">faq</h1> <section class="faq"> <p class="question">How many team members can i invite?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> You can invite up to 2 additional users on the Free plan. There is no limit on team members for the Premium plan. </p> </section> <section class="faq"> <p class="question">What is the maximum file upload size?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> No more than 2GB. All files in your account must fit your allotted storage space. </p> </section> <section class="faq"> <p class="question">How do I reset my password?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> Click “Forgot password” from the login page or “Change password” from your profile page. A reset link will be emailed to you. </p> </section> <section class="faq"> <p class="question">Can I cancel my subscription?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> Yes! Send us a message and we'll process your request no questions asked. </p> </section> <section class="faq"> <p class="question">Do you provide additional support?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> Chat and email support is available 24/7. Phone lines are open during normal business hours. </p> </section> </div>

You can also fix it by simply keeping a reference ( currentlyOpen ) of the currently opened section and using it in your event listener.

It would only require you to change some js and keep the same css.

Here's a little bit of code to explaining the change:

let currentlyOpen; // Keep track of the currently openend section.

function toggle() {
    if (currentlyOpen) {
    // Something currently open
    currentlyOpen.lastElementChild.style.display = "none" // Close it
  }
  
  if (currentlyOpen === this) {
    // The element the user clicked on is already opened, close it!
    currentlyOpen.lastElementChild.style.display = "none" // Close it
    currentlyOpen = undefined; // Delete it's refrence
    return // And return (stop execution)
  }
  
  // Show clicked element
  this.lastElementChild.style.display = "block"
  
  // Store it for later
  currentlyOpen = this
}

For more info see the code snipped:

 const questions = document.querySelectorAll("section"); const answers = document.querySelectorAll(".answer"); let currentlyOpen; function toggle() { if (currentlyOpen) { // Something currently open currentlyOpen.lastElementChild.style.display = "none" // Close it } if (currentlyOpen === this) { // Element is already open currentlyOpen.lastElementChild.style.display = "none" // Hide it currentlyOpen = undefined; // Delete it's refrence return // And return } // Show clicked element this.lastElementChild.style.display = "block" // Store it for later currentlyOpen = this } questions.forEach((question) => question.addEventListener("click", toggle));
 .card { width: 20.4375rem; background-color: #ffffff; border-radius: 1.4375rem; margin: auto; padding: 132px 24px 48px; text-align: center; transform: translateY(-125px); } section { display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #e8e8ea; flex-wrap: wrap; } p { color: var(--main-text-color); font-size: 0.75rem; } .answer { flex-basis: 100%; text-align: left; display: none; }
 <div class="card"> <h1 class="no">faq</h1> <section> <p class="question">How many team members can i invite?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> You can invite up to 2 additional users on the Free plan. There is no limit on team members for the Premium plan. </p> </section> <section> <p class="question">What is the maximum file upload size?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> No more than 2GB. All files in your account must fit your allotted storage space. </p> </section> <section> <p class="question">How do I reset my password?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> Click “Forgot password” from the login page or “Change password” from your profile page. A reset link will be emailed to you. </p> </section> <section> <p class="question">Can I cancel my subscription?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> Yes! Send us a message and we'll process your request no questions asked. </p> </section> <section> <p class="question">Do you provide additional support?</p> <img class="arrow" src="./images/icon-arrow-down.svg" alt="" /> <p class="answer"> Chat and email support is available 24/7. Phone lines are open during normal business hours. </p> </section> </div>

Or a working version hosted on jsfiddle: https://jsfiddle.net/fmr9tvw8/

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