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");
});
});
})