Search code examples
javascripthtmlcssresponsive-designsvelte

Svelte Responsive Menu does not show vertical items on narrow screen


I can't get this W3C Responsive Menu to work in Svelte. The menu horizontal navbar is correctly hidden, but I cannot get the vertical version to show. The demo on the W3C site works.

When I build it locally...

the horizontal menu items work in a desktop setting and all but the first are correctly hidden when the width is less than 600px.

I had to change the myFunction function so that when I press the burger menu, the responsive class is correctly added and removed.

However, when I do press it, the menu items are not displayed at all (vertically or otherwise).

I have created a simple Svelte playground and that mostly works in a similar way, although pressing the burger menu refreshes the page, probably due to the a tag.

Here is the code...

<div class="topnav" id="myTopnav">
    <a href="#home" class="active">Home</a>
    <a href="#news">News</a>
    <a href="#contact">Contact</a>
    <div class="dropdown">
        <button class="dropbtn">Dropdown 
            <i class="fa fa-caret-down"></i>
        </button>
        <div class="dropdown-content">
            <a href="#">Link 1</a>
            <a href="#">Link 2</a>
            <a href="#">Link 3</a>
        </div>
    </div> 
    <a href="#about">About</a>
    <a href="#" class="icon" onclick={(event) => myFunction()}>&#9776</a>
</div>

<script>
    /* Toggle between adding and removing the "responsive" class to topnav when the user clicks on the icon */
    function myFunction() {
        const topnav = document.getElementById("myTopnav");
        if (topnav.classList.contains("responsive")) {
            topnav.classList.remove("responsive"); // Collapse menu
        } else {
            topnav.classList.add("responsive"); // Expand menu
        }
        console.log(topnav.className);
    }

    /* not using this now as didn't add class*/
    function OriginalMyFunction() {
      var x = document.getElementById("myTopnav");
      if (x.className === "topnav") {
        x.className += " responsive";
      } else {
        x.className = "topnav";
      }
    }
</script>

<style>
    .topnav {
        overflow: hidden;
        background-color: #333;
    }

    .topnav a {
              font-size: 17px;
        float: left;
        display: block;
        color: #f2f2f2;
        text-align: center;
        padding: 14px 16px;
        text-decoration: none;
    }

    .active {
        background-color: #04AA6D;
        color: white;
    }

    .topnav .icon {
        display: none;
    }

    .dropdown {
        float: left;
        overflow: hidden;
    }

    .dropdown .dropbtn {
              font-size: 17px;
        border: none;
        outline: none;
        color: white;
        padding: 14px 16px;
        background-color: inherit;
        font-family: inherit;
        margin: 0;
    }

    .dropdown-content {
        display: none;
        position: absolute;
        background-color: #f9f9f9;
        min-width: 160px;
        box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
        z-index: 1;
    }

    .dropdown-content a {
        float: none;
        color: black;
        padding: 14px 16px;
        text-decoration: none;
        display: block;
        text-align: left;
    }

    .topnav a:hover, .dropdown:hover .dropbtn {
        background-color: #555;
        color: white;
    }

    .dropdown-content a:hover {
        background-color: #ddd;
        color: black;
    }

    .dropdown:hover .dropdown-content {
        display: block;
    }

    @media screen and (max-width: 600px) {
        .topnav a:not(:first-child), .dropdown .dropbtn {
            display: none;
        }
            .topnav a.icon {
                float: right;
                display: block;
        }
    }

    @media screen and (max-width: 600px) {
        .topnav.responsive {position: relative;}
        .topnav.responsive .icon {
            position: absolute;
            right: 0;
            top: 0;
        }
        .topnav.responsive a {
            float: none;
            display: block;
            text-align: left;
        }
        .topnav.responsive .dropdown {float: none;}
        .topnav.responsive .dropdown-content {position: relative;}
        .topnav.responsive .dropdown .dropbtn {
            display: block;
            width: 100%;
            text-align: left;
        }
    }

Solution

  • It looks like the Svelte compiler is optimizing away your "unused" CSS referencing the "responsive" class since the class isn't initially present when the page loads.

    So, one solution is to declare the related CSS as "global" to disable this optimization. Namely, you would change this related CSS block as follows:

    @media screen and (max-width: 600px) {
        :global(.topnav.responsive) {position: relative;}
        :global(.topnav.responsive) .icon {
            position: absolute;
            right: 0;
            top: 0;
        }
        :global(.topnav.responsive) a {
            float: none;
            display: block;
            text-align: left;
        }
        :global(.topnav.responsive) .dropdown {float: none;}
        :global(.topnav.responsive) .dropdown-content {position: relative;}
        :global(.topnav.responsive) .dropdown .dropbtn {
            display: block;
            width: 100%;
            text-align: left;
        }
    }
    

    See this related answer for a deeper dive and some alternate solutions.

    Also, to prevent your Svelte sandbox reloading when you press the "burger" button you can enable the "preventDefault" directive in your "onClick" handler attribute like this:

    <a href="#" class="icon" on:click|preventDefault={(event) => myFunction()}>&#9776</a>