Latency-friendly hierarchical menus using Unicode bullets and a bit of JavaScript

Technologies: HTML 4+, CSS 2+, JavaScript 1.7+, Modern web browser

JavaScript can expand hierarchical menus in place, without a page reload, but often these scripts are too fancy for their own good. Large scripts and lots of pretty icon images slow down page loads while trying to make fast menus. The result is a net loss and a slower web site. The main problem is network latency, which slows down loading external icon image and JavaScript files. Instead, use Unicode symbol characters to replace custom image icons, then minimize and embed the JavaScript on the page. The result is smaller, it has no external files adding latency delays, and it enables web pages to load much faster.

Latency and hierarchical menus

Network latency is the key problem slowing down web sites today: it adds a delay to get each web page file. The delays add up fast so that a site with lots of JavaScript, CSS, and image files loads slowly, even if those files are small. A simple fast solution for hierarchical menus needs to minimize external files.

This article's hierarchical menu solution (shown to the right):

Hierarchical menu

  • Use Unicode symbol characters instead of icon images. Unicode symbols work in all modern browsers, there are thousands to choose from, there is no external icon file to download, and thus no latency to slow down the page load. The article on Latency-friendly customized bullets using Unicode characters introduces the idea.
  • Use JavaScript embedded in the web page. There is no JavaScript file to download, and no latency delay to get it. The JavaScript is small, fast, and works in all browsers.
  • Use <ul> lists and expand nested menu <ul> tags. They work in all browsers and they degrade gracefully to a static menu if JavaScript is disabled. We can also make them look like familiar hierarchical lists found on Windows, Mac OS X, and Linux.

A common argument says none of this hassle is needed — go ahead and use big JavaScripts and lots of icon images because they get cached anyway. However, a study by Yahoo! found that most visitors to their sites had none of the site's files in their cache. Visitors had to re-download them on every visit. Browser caches just aren't big enough to hold files for long. To make a first arrival fast, we need to minimize external JavaScript and image files and embed the JavaScript within the page. And keep it small too, of course.

Create the menu markup

Build a hierarchical menu using <ul> and <li> tags. We'll need to swap out the standard bullets for Unicode bullets that can toggle between "opened" and "closed" states when the visitor clicks on them. To do this, add an empty <span> to hold the bullet at the start of each <li>. To give CSS and JavaScript control over the hierarchical menu, use a few class names:

  • "hmenu" for the top-level <ul>.
  • "hmenu-submenu" for each <li> that has a nested menu <ul>.
  • "hmenu-item" for each <li> that doesn't have a nested menu.
  • "hmenu-bullet" for the <span> in each <li>.
Hierarchical menu markup
HTML <ul class="hmenu">
  <li class="hmenu-item"><span class="hmenu-bullet"></span>Item</li>
  <li class="hmenu-submenu"><span class="hmenu-bullet"></span>Nested menu
    <ul>
      <li class="hmenu-item"><span class="hmenu-bullet"></span>Item</li>
      <li class="hmenu-submenu"><span class="hmenu-bullet"></span> Nested menu
        <ul>
          ...
        </ul>
      </li>
    </ul>
  </li>
</ul>
CSS .hmenu li { list-style: none; line-height: 1.5em; padding-left: 0; }
.hmenu ul { padding-left: 1em; margin-left: 0; }
.hmenu-item { }
.hmenu-submenu { }
.hmenu-bullet
{ float: left; width: 1.6em; color: #AAA; }

Choose the menu bullets

Unicode has thousands of symbol characters to choose from. In HTML, use &#xXXXX; where XXXX is the four-digit hex number of the Unicode character. For instance, &#x25BA; is a right pointing triangle: ►. See the end of the article on Latency-friendly customized bullets using Unicode characters for a table of bullets. Fileformat.info also has excellent pages listing Unicode characters.

While you can use any characters you like for menu bullets, let's look at characters that mimic the look of hierarchical lists on Windows, Mac OS X, and Linux. Such lists are found in lists of options, features, and files. We won't include document and folder icons since we're making a menu, not a file list.

  Image Bullets Menu
Windows XP Windows XP Explorer Item = &nbsp; = space
Closed = &#x229E;
= ⊞
Opened = &#x2295; = ⊟
  •  Item
  • Menu closed
  • Menu opened
    •  Item
    • Menu closed
    • Menu opened
      •  Item
      •  Item
Windows Vista Windows Vista Explorer Item = &nbsp; = space
Closed = &#x25B7;
= ▷
Opened = &#x25E2; = ◢
  •  Item
  • Menu closed
  • Menu opened
    •  Item
    • Menu closed
    • Menu opened
      •  Item
      •  Item
Mac OS X Mac OS X Finder Item = &nbsp; = space
Closed = &#x25BA;
= ►
Opened = &#x25BC; = ▼
  •  Item
  • Menu closed
  • Menu opened
    •  Item
    • Menu closed
    • Menu opened
      •  Item
      •  Item
Linux Linux File Manager Item = &nbsp; = space
Closed = &#x25B7;
= ▷
Opened = &#x25BD; = ▽
  •  Item
  • Menu closed
  • Menu opened
    •  Item
    • Menu closed
    • Menu opened
      •  Item
      •  Item

While these Unicode symbols work in all current web browsers, there are still some differences in how the characters look. Safari 3 on a Mac or Windows shows all of the above characters identically and correctly. Firefox, Opera, and Internet Explorer 7 show the ⊞ and/or ▷ characters at the wrong size. For any use of Unicode symbols, cross-platform testing is still necessary. Fortunately, support is getting better and more uniform with each new browser update.

Add the "onclick" menu behavior

Clicking on a menu's bullet should open or close that nested menu. So, for each menu <span>, add an "onclick" behavior that calls "toggle_submenu(this);".

onclick behavior for hierarchical menu bullets
HTML <li class="hmenu-submenu"><span class="bullet" onclick="toggle_submenu(this);"></span>Nested menu
JavaScript ITEM  = '\u00A0';
OPEN  = '\u25BC';
CLOSE = '\u25BA';

function toggle_submenu(e) {
  e.innerHTML=(e.innerHTML==OPEN) ? CLOSE : OPEN;
  for (var c, p=e.parentNode, i=0; c=p.childNodes[i]; i++)
    if (c.tagName=='UL') c.style.display=(c.style.display=='none') ? 'block' : 'none';
}

The toggle_submenu JavaScript function toggles between two bullet symbols: OPEN and CLOSE. These are initialized to Unicode symbols with \uXXXX, where XXXX is the hex number for the character. For instance, \u25BC is a down pointing triangle, ▼, and \u25BA is a right pointing triangle, ►. You can make these anything, of course, such as the bullets above for a look like Windows XP, Windows Vista, Linux, or Mac OS X.

After toggling the bullets, toggle_submenu looks for <ul> children of the same <li> and toggles their display between "block" (visible) and "none" (invisible). This opens and closes the nested menu.

Add the "onload" menu behavior

On a page load, the initial bullets need to be set and all <ul> nested menus closed. So, either add an "onload" behavior to <body>, or add a <script> tag at the end of the page that calls "reset_menus();".

onload behavior to reset hierarchical menu bullets
HTML <body onload="reset_menus();">
    OR
<script language="javascript">reset_menus();</script>
JavaScript ITEM  = '\u00A0';
OPEN  = '\u25BC';
CLOSE = '\u25BA';

function toggle_submenu(e) {
  e.innerHTML=(e.innerHTML==OPEN) ? CLOSE : OPEN;
  for (var c, p=e.parentNode, i=0; c=p.childNodes[i]; i++)
    if (c.tagName=='UL') c.style.display=(c.style.display=='none') ? 'block' : 'none';
}

function reset_menus() {
  var li_tags=document.getElementsByTagName('LI');
  for (var li, i=0; li=li_tags[i]; i++) {
    if (li.className.match('hmenu-item'))
      for (var c, j=0; c=li.childNodes[j]; j++)
        if (c.tagName=='SPAN' && c.className.match('hmenu-bullet')) { c.innerHTML=ITEM; }
    if (li.className.match('hmenu-submenu'))
      for (var c, j=0; c=li.childNodes[j]; j++)
        if (c.tagName=='SPAN' && c.className.match('hmenu-bullet')) { c.innerHTML=CLOSE; }
        else if (c.tagName=='UL') { c.style.display = 'none'; }
  }
}

The reset_menus JavaScript function looks for all <li> tags in the document. If the <li> has class "hmenu-item", its bullet <span> is set to the ITEM symbol (a non-breaking space in this example). If the <li> has class "hmenu-submenu", its bullet <span> is set to CLOSE and child <ul> tags are set to display "none" to close the menu.

The result is a hierarchical menu where nested menus open and close by clicking the bullet beside them. If JavaScript is not enabled, the hierarchical menu defaults to fully open with no special bullets. The JavaScript is small, fast, and can be included in a <script> tag for every page. And there are no external icon image or JavaScript files, and thus no latency delays to slow down page loading.

Here, try it:

Further reading

Comments

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • Web page addresses and e-mail addresses turn into links automatically.

More information about formatting options

Nadeau software consulting
Nadeau software consulting