0
votes

I'm building a nav menu where the desktop version shows sub-menus as drop-downs that appear on hover, and the mobile version has accordion style sub-menus that are triggered on click/touch.

I have both of these set up using the (simplified) code below, but I want to be able to swap the event listeners as the browser window is resized. I've been trying to use resizeObserver to remove and re-add the event listeners, but I'm having trouble unbinding the listeners correctly.

const viewportWidth = window.innerWidth;
const menuItems = document.querySelectorAll('[data-menu-item]');

function setupMenu() {


    menuItems.forEach((item) => {
        const itemHandler = toggleState.bind(null, item);

        if (viewportWidth > 960) {
            item.addEventListener('mouseover', itemHandler);
            item.addEventListener('mouseout', itemHandler);
        } else {
            item.addEventListener('click', itemHandler);
        }
    });

}
1
You shouldn't try to swap event listeners, you should swap HTML elements... - ikiK
Move your viewportWidth > 960 to inside itemHandler so to check for width when event fires, not on init - Justinas
I would advise against adding and removing handlers. Check your condition(s) inside your handler callback functions instead, and make them simply do nothing, if not applicable. - CBroe
Side note: not all touch devices are limited to 960 pixels. - trincot

1 Answers

1
votes

Instead of swapping listeners on an element, you should use two seperate elements, each with one of the event listeners, and then use a CSS media query to set display: none on the one you want hidden.

Here's an overly simplified example with just CSS, if you really want to use JS for this then you can set the element.style.display property, but using CSS is more performant and will work even when the screen is resized multiple times.

#top-bar {
  display: block;
}

#side-bar {
  display: none;
}

@media screen and (min-width: 960px) {
  #top-bar {
    display: none;
  }
  #side-bar {
    display: block;
  }
}
<div id="top-bar">top</div>
<div id="side-bar">side</div>