Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Collapse a horizontal menu for response design

I have a menu which looks like this:

|Home|Options|Settings|Tools|Preferences|Edit|

That's fine when a phone has lots of horizontal space, but when a device with a narrow viewport accesses the page, I want the menu to look like

|Home|Options|Settings|+MORE+|

Where clicking the "MORE" menu displays the other items in a vertical drop down.

I don't want to set manual breakpoints, because I have no idea how wide the individual menu items will be when displayed.

My menu is currently just a set of <li> in a <ul>

The CSS for horizontal layout is

#menu ul, #menu li { margin: 0; padding: 0; list-style: none; }
#menu ul { overflow: auto; }
#menu li { float: left; }
#menu  a { display: block; padding: 0.5em; text-decoration: none; border-right: 1px solid #fff; font-size: 110%; }

I'm reluctant to use something like jQuery - even when minimized, it's still a significant overhead for older mobile browsers. Media Queries are also problematic for some phones, so I would like to avoid relying on those.

Any thoughts on CSS and (simple) JavaScript to automatically hide elements depending on the browser's width?

like image 706
Terence Eden Avatar asked Sep 18 '12 15:09

Terence Eden


People also ask

What is a collapse menu?

A collapsible menu is a vertical list menu that collapses down to expandable menu titles when displayed on mobile devices. These are used almost exclusively in the footer and can be used in grids to make columns of collapsible menus.

How do you collapse a menu in HTML?

To control (show/hide) the collapsible content, add the data-toggle="collapse" attribute to an <a> or a <button> element. Then add the data-target="#id" attribute to connect the button with the collapsible content (<div id="demo">).


1 Answers

Actually, you can do it with no JavaScript at all, just with media queries (which really have excellent support + this solution I am presenting is a mobile first one) and :nth-last-child (which again is even supported by Opera Mini).

demo

(resize to see how it works)

You'll need to have a structure like this:

<nav id='menu'>
    <ul>
        <li><a href='#'>Home</a></li>
        <li><a href='#'>Options</a></li>
        <li><a href='#'>Settings</a></li>
        <li><a href='#'>Tools</a></li>
        <li><a href='#'>Preferences</a></li>
        <li><a href='#'>Edit</a></li>
        <li><a href='#'>+ MORE +</a></li>
    </ul>
</nav>

Then you'll need to select the Tools, Preferences and Edit and set their display to none:

#menu li:nth-last-child(-n+4):not(:last-child) { display: none; }

li:nth-last-child(-n+4) selects only the first four list items from the end. You add the :not(:last-child) condition to that because you want the + MORE + list item to be shown.

In order to better understand structural pseudo-classes, you can play around with this tool.

Finally, you'll need to use a media query to change the display settings for larger screens:

@media (min-width: 30em) {
    #menu li:nth-last-child(-n+4):not(:last-child) { display: block; }
    #menu li:last-child { display: none; }
}

I am using an em based media query and not a px based one for two reasons:

  • one, this article;
  • two, my own site looks like crap on zoom because I used px based media queries on it a year ago;

EDIT: In order to make the menu expand on click and to make the number of menu elements shown vary with screen width, I have changed the structure a bit more:

<nav id='menu'>
    <a tabindex=1 class='ctrl' href='#'>+ MORE +</a>
    <ul>
        <li><a href='#' class='menu-link'>Home</a></li>
        <li><a href='#' class='menu-link'>Options</a></li>
        <li><a href='#' class='menu-link'>Settings</a></li>
        <li><a href='#' class='menu-link'>Tools</a></li>
        <li><a href='#' class='menu-link'>Preferences</a></li>
        <li><a href='#' class='menu-link'>Edit</a></li>
    </ul>
</nav>

And also changed the CSS a bit:

#menu .ctrl { float: right; }
#menu ul, #menu li { margin: 0; padding: 0; list-style: none; }
#menu ul { overflow: auto; }
#menu li { float: left; }
#menu li:nth-last-child(-n+5) { display: none; }
#menu a {
    padding: 0.5em;
    text-decoration: none;
    border-right: 1px solid #fff;
    font-size: 110%;
}
#menu li a { display: block; }
#menu li:first-child a { border-left: 1px solid #fff; }
#menu .ctrl:focus, #menu .ctrl:active { display: none; outline: 0; }
#menu .ctrl:focus ~ ul li, #menu .ctrl:active ~ ul li { display: block; }

@media (min-width: 15em) {
    #menu li:nth-child(2) { display: block; }
}
@media (min-width: 20em) {
    #menu li:nth-child(3) { display: block; }
}
@media (min-width: 25em) {
    #menu li:nth-child(4) { display: block; }
}
@media (min-width: 30em) {
    #menu .ctrl ~ ul li { display: block; }
    #menu .ctrl { display: none; }
}

demo

(I've also added a background with vertical lines at every 5em just to make it clear how wide the screen is when resizing the browser window)

This method should work without JavaScript - tested that in desktop browsers, Opera Mobile, Android browser and iOS Safari. I don't know about Opera Mini though - I'll have to test that.

EDIT#2: No, it doesn't work in Opera Mini for me (the menu is collapsed, but clicking the + MORE + link does not expand it). Tried to make it work with JavaScript (no library), but that also doesn't work in Opera Mini (though it works on desktop browsers).

EDIT#3: Also tried to do the same thing using jQuery. This time it also works in Opera Mini. Really slow (at least for me), but it works. This is what I used:

$('.ctrl').click(function() {
    $(this).css({'display': 'none'}).next().children().css({'display': 'block'});
});

EDIT#4: Now tried the :target method - demo (also CSS-only). Works fine on my laptop using Chrome (haven't tested in another desktop browser), doesn't work in Opera Mini (menu is collapsed, clicking the + MORE + link does not expand it). Works in Opera Mobile though.

like image 179
Ana Avatar answered Sep 28 '22 16:09

Ana