I've been trying to make a dropdown button that it disactivates when clicking outside of it but I have not find good solution that has good performance, I am making the website with the Astro Framework
so this is an example of what I want it to be like: example
and this is what I have:my website
this is my code:
<nav
class="hidden items-center lg:relative lg:mt-0 lg:!flex lg:basis-auto"
>
<!-- Desktop Navigation -->
<ul
class="list-style-none mr-auto flex flex-col pl-0 lg:flex-row"
>
{navData.map(data => {
if(data.dropdown === true) {
return (
<li data-title={data.title} class="sm:block relative dropdowns mx-2 lg:flex ">
<button data-title={data.title} class="dropdown text-sm rounded flex gap-1 items-center">
{data.title}
<svg viewBox="0 0 1024 1024" class="icon w-3" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path d="M903.232 256l56.768 50.432L512 768 64 306.432 120.768 256 512 659.072z" ></path></g></svg>
</button>
<div data-title={data.title} class="absolute right-0 top-7 mt-2 w-48 bg-gray-800 rounded-md overflow-hidden z-10 hidden">
{data.subcat.map(el => (
<a href={el.slug} class="block px-4 py-2 text-sm text-gray-200 hover:bg-gray-700">
{el.title}
</a>
))}
</div>
</li>
)}
else {
return (
<li class="mb-4 pl-2 lg:mb-0 lg:pl-0 lg:pr-1" data-te-nav-item-ref>
<a
class="p-0 text-sm transition duration-200 hover:ease-in-out motion-reduce:transition-none lg:px-2"
href={`${data.slug}`}
data-te-nav-link-ref
>
{data.title}
</a>
</li>
)
}
})}
</ul>
</nav>
const dropdownToggle = document.querySelectorAll('.dropdown');
const dropdownMenu = document.querySelectorAll('.dropdowns > div');
dropdownToggle.forEach(button => button.addEventListener('click', () => {
dropdownMenu.forEach((el) => {
if(button.getAttribute('data-title') === el.getAttribute('data-title')){
el.classList.toggle('hidden')
}
});
}))
I've tried to use focus-within but didn't work as expected
.dropdowns:focus-within > div {
display: block;
}
and I think this solution:
window.onclick = function(event) {
if (!event.target.matches('.dropbtn')) {
var dropdowns = document.getElementsByClassName("dropdown-content");
var i;
for (i = 0; i < dropdowns.length; i++) {
var openDropdown = dropdowns[i];
if (openDropdown.classList.contains('show')) {
openDropdown.classList.remove('show');
}
}
}
}
is bad for performance
It is not bad for performance, because you need a click handler that will notice whenever a dropdown is to be closed. You should not optimize what's not slow, because there are chances that you will overcomplicate your code with something that will be difficult to maintain and possibly slower too.
But, if you indeed notice some performance issues (like in the case when you have many thousands of dropdowns), then you can simply create some resource, let's call for the sake of simplicity currentDropdown
and:
currentDropdown
and that does not match to the dropdown you just opened (if you opened a dropdown), then collapse itcurrentDropdown
to itcurrentDropdown
to undefined
currentDropdown
to undefined
So, instead of looping all dropdowns, you will have a single dropdown to handle at most.
If you really really want to avoid having a click event outside the dropdowns when none of them are open (but I don't recommend this), then you can removeEventListener
whenever you click outside of any dropdown so that clicks outside of dropdowns will not trigger event listeners when you do not need it. But, again, I do not recommend this, because it's code complication with virtually no gain. Avoiding the loop as described above should be enough.