Home > OS >  how to make element rotate on click
how to make element rotate on click

Time:10-22

Im making an accordion and wonder how to make an arrow (lets suppose its an arrow svg icon) rotate 180deg once an accordion element is clicked and expanded in Vanilla JS?

Currently it only rotates the first element from the dom found that is matching my selected tag, because of querySelector and not querySelectorAll. How to use querySelectorAll properly in this case so not all of them get rotated?

document.querySelectorAll("details").forEach((accordion) => {
    accordion.addEventListener("click", () => {
      document.querySelectorAll("details").forEach((event) => {
        document.querySelector("details span").classList.add("active");
        if (accordion !== event) {
          event.removeAttribute("open");
        }
      });
    });
  });
summary{ 
  list-style: none 
}

details{
  padding: 1rem;
  background-color: lightblue;

 }

details summary{
  display: flex;
  justify-content: space-between;
}

details:nth-child(even){
  background-color: lightgreen;
}

p{
  padding-top: 0.3rem;
}

.active{
  transform: rotate(180deg);
}

span{
display: block;
}
<div>
  <details>
    <summary>    
      <h3>item1</h3> 
      <span>arrow</span> </summary>
    <p>desc1</p>
  </details>
  <details>
    <summary>
      <h3>item2</h3> 
      <span>arrow</span> 
    </summary> 
    <p>desc2</p>
  </details>
  <details>
    <summary> 
      <h3>item3</h3> 
      <span>arrow</span> 
    </summary>
    <p>desc3</p>
  </details>
</div>

CodePudding user response:

Once your accordion is clicked you are running a loop through all the accordions and performing actions. But you only need to look at the span which is child of the clicked accordion.

As identified, this statement selects only the first span from first details : document.querySelector("details span").classList.add("active");.

You can use this. this inside eventlistener callbacks always give the element on which event is attached. (You cannot use arrow functions with this). That is why I have converted them to non arrow functions.

I am following the same logic as yours but after removing from all the accordions, I am modifying the properties of the children of the current accordion. Have a look:

document.querySelectorAll("details").forEach((accordion) => {
    accordion.addEventListener("click", function () {
      document.querySelectorAll("details").forEach(function (elem) {
          elem.removeAttribute("open");
          elem.querySelector("span").classList.remove("active");
        }); this.querySelector("span").classList.add("active");

    });
  });
summary{ 
  list-style: none 
}

details{
  padding: 1rem;
  background-color: lightblue;

 }

details summary{
  display: flex;
  justify-content: space-between;
}

details:nth-child(even){
  background-color: lightgreen;
}

p{
  padding-top: 0.3rem;
}

.active{
  transform: rotate(180deg);
}

span{
display: block;
}
<div>
  <details>
    <summary>    
      <h3>item1</h3> 
      <span>arrow</span> </summary>
    <p>desc1</p>
  </details>
  <details>
    <summary>
      <h3>item2</h3> 
      <span>arrow</span> 
    </summary> 
    <p>desc2</p>
  </details>
  <details>
    <summary> 
      <h3>item3</h3> 
      <span>arrow</span> 
    </summary>
    <p>desc3</p>
  </details>
</div>

CodePudding user response:

You are making use of event listeners but not the actual event data. Once your event listener is triggered the first parameter will contain all the data you need. In your case it would be as simple as adding a parameter to your event listener

accordion.addEventListener("click", (e) => {
    //...
}

I used 'e' as name but it could be 'event' or anything you find most readable. Then you can use e like it's the exact element you're targeting, for example:

e.target.classlist.toggle("active");

you can even target it's parent/child elements by doing

e.target.parentElement.classlist.toggle("active");

e.target.querySelector("span")

Putting the query selector on the target narrows down where it's looking all the way down to your details element which I think is exactly what you want here. My final solution would be

document.querySelectorAll("details").forEach((accordion) => {
    accordion.addEventListener("click", (e) => {
        document.querySelectorAll("detail").forEach((element) => {
            element.open = false
            element.querySelector("span").classList.remove("active");
        });
        e.target.open = true;
        e.target.querySelector("span").classList.add("active");
      });
    });
  })

  • Related