By 2001, JavaScript had become powerful enough to manipulate HTML elements in real time - changing their styles, positions, and visibility without a page reload. The technology had a name: DHTML, or Dynamic HTML. It was not a new language or a specification; it was a marketing term for the combination of JavaScript, CSS, and the Document Object Model used to create interactive effects.
The killer application of DHTML in 2001 was the dropdown navigation menu. Every corporate website wanted one. Hover over "Products," a submenu drops down showing subcategories. Hover over "Services," a different submenu appears. No page reload. No Flash. Pure HTML, CSS, and JavaScript.
Building it to work in both Internet Explorer 5 and Netscape 6 was a genuine engineering challenge.
The Browser Compatibility Problem
IE5 and Netscape 6 both supported the W3C DOM (document.getElementById, element.style), but they differed in how events bubbled and how onmouseover worked across nested elements. The earlier Netscape 4 used a completely different document.layers model that we will ignore - by 2001, Netscape 4's market share had fallen enough to treat it as a known limitation.
The event model problem:
// IE5: onmouseover/onmouseout fired on the element AND all child elements
// Netscape 6: same behavior - W3C bubbling model
// But the specific event object properties differed:
// IE5 event model:
element.onmouseover = function() {
var target = window.event.srcElement; // IE-specific
var relatedTarget = window.event.fromElement;
};
// Netscape/W3C event model:
element.onmouseover = function(e) {
var target = e.target; // W3C standard
var relatedTarget = e.relatedTarget;
};
// Cross-browser wrapper - we all had this:
function getEvent(e) {
return e || window.event;
}
function getTarget(e) {
return e.target || e.srcElement;
}
function getRelatedTarget(e) {
return e.relatedTarget || e.fromElement;
}
The Complete Dropdown Menu Implementation
The approach: absolutely-positioned <div> elements hidden by default, shown on onmouseover, hidden on onmouseout. The trick was handling the mouse moving from the parent link into the submenu - this fired onmouseout on the parent, which would close the menu before the user could click anything.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>DHTML Navigation Menu - 2001</title>
<style type="text/css">
/* Reset - browsers in 2001 had wildly different default margins */
body, td, th, p { font-family: Arial, Helvetica, sans-serif; font-size: 13px; }
/* Top-level navigation bar */
#navbar {
width: 100%;
background-color: #336699;
height: 28px;
position: relative; /* needed for absolute child positioning to work */
}
/* Each top-level nav item - positioned inline */
.nav-item {
position: relative;
display: inline;
float: left;
}
.nav-item a {
display: block;
float: left;
padding: 5px 14px;
color: #ffffff;
text-decoration: none;
font-weight: bold;
font-size: 12px;
}
.nav-item a:hover {
background-color: #4477aa;
}
/* Dropdown submenu container - hidden by default */
.submenu {
position: absolute;
top: 28px; /* height of navbar */
left: 0;
width: 160px;
background-color: #f5f5f5;
border: 1px solid #336699;
border-top: 2px solid #336699;
display: none; /* hidden until mouseover */
z-index: 100; /* above page content */
}
/* Submenu links */
.submenu a {
display: block;
padding: 5px 10px;
color: #333333;
text-decoration: none;
border-bottom: 1px solid #dddddd;
font-weight: normal;
}
.submenu a:hover {
background-color: #d0e0f0;
color: #003366;
}
</style>
</head>
<body>
<div id="navbar">
<!-- Each nav item has an id for JavaScript targeting -->
<div class="nav-item" id="nav-products">
<a href="products.html"
onmouseover="showMenu('menu-products', 'nav-products')"
onmouseout="scheduleHide('menu-products')">
Products ▼
</a>
<div class="submenu" id="menu-products"
onmouseover="cancelHide('menu-products')"
onmouseout="scheduleHide('menu-products')">
<a href="products/software.html">Software</a>
<a href="products/hardware.html">Hardware</a>
<a href="products/services.html">Services</a>
<a href="products/support.html">Support</a>
</div>
</div>
<div class="nav-item" id="nav-about">
<a href="about.html"
onmouseover="showMenu('menu-about', 'nav-about')"
onmouseout="scheduleHide('menu-about')">
About Us ▼
</a>
<div class="submenu" id="menu-about"
onmouseover="cancelHide('menu-about')"
onmouseout="scheduleHide('menu-about')">
<a href="about/company.html">Company</a>
<a href="about/team.html">Our Team</a>
<a href="about/careers.html">Careers</a>
</div>
</div>
<div class="nav-item" id="nav-contact">
<a href="contact.html"
onmouseover="showMenu('menu-contact', 'nav-contact')"
onmouseout="scheduleHide('menu-contact')">
Contact ▼
</a>
<div class="submenu" id="menu-contact"
onmouseover="cancelHide('menu-contact')"
onmouseout="scheduleHide('menu-contact')">
<a href="contact/office.html">Our Office</a>
<a href="contact/form.html">Send Message</a>
</div>
</div>
</div>
<script type="text/javascript">
// DHTML dropdown menu logic - 2001 style
// Requires: IE5+, Netscape 6+, Opera 5+
var hideTimers = {}; // Track delayed-hide timers per menu
// Show a submenu, close all others
function showMenu(menuId, navItemId) {
// Close any other open menu first
hideAllMenus();
var menu = document.getElementById(menuId);
if (menu) {
menu.style.display = 'block';
}
// Cancel any pending hide for this menu
cancelHide(menuId);
}
// Schedule a menu to hide - delayed so mouse can move into submenu
function scheduleHide(menuId) {
// Cancel any existing timer for this menu
if (hideTimers[menuId]) {
clearTimeout(hideTimers[menuId]);
}
// Hide after 300ms - enough time to move mouse into submenu
hideTimers[menuId] = setTimeout(function() {
hideMenu(menuId);
}, 300);
}
// Cancel a scheduled hide (mouse moved into submenu)
function cancelHide(menuId) {
if (hideTimers[menuId]) {
clearTimeout(hideTimers[menuId]);
hideTimers[menuId] = null;
}
}
function hideMenu(menuId) {
var menu = document.getElementById(menuId);
if (menu) {
menu.style.display = 'none';
}
hideTimers[menuId] = null;
}
function hideAllMenus() {
var menus = document.getElementsByTagName('div');
for (var i = 0; i < menus.length; i++) {
if (menus[i].className === 'submenu') {
menus[i].style.display = 'none';
}
}
}
// Close all menus if user clicks anywhere on the page
document.onclick = function() {
hideAllMenus();
};
</script>
</body>
</html>
The setTimeout Trick
The most important technique in that implementation is the 300ms delayed hide. Without it:
- User hovers over "Products" → submenu appears
- User moves mouse from "Products" link toward submenu
onmouseoutfires on "Products" link immediately- Submenu disappears before the user reaches it
The fix: don't hide immediately. Schedule the hide with setTimeout. When the mouse enters the submenu, call cancelHide to clear the timer. The submenu stays open as long as the mouse is anywhere within it.
// Why 300ms? Empirical testing in 2001.
// 100ms: too fast - slow mice / trackballs still lost the menu
// 500ms: menus felt "sticky" - lingered after intentional exit
// 300ms: right balance on the hardware common in 2001
// (the same heuristic appears in modern tooltip libraries today)
The z-index War
In 2001, z-index behaved inconsistently across browsers. IE5 had a notorious bug: <select> elements (HTML dropdowns) always rendered on top of absolutely-positioned elements, regardless of z-index. Your beautiful DHTML menu would drop down behind a <select> on the page.
The fix: place an <iframe> behind your menu, sized to cover it. The iframe created a new stacking context that covered <select> elements.
// IE5/IE6 select-overlap fix - the "iframe shim"
// Required on any page that had <select> elements near the nav
function createIframeShim(menuElement) {
var shim = document.createElement('iframe');
shim.src = 'about:blank';
shim.style.position = 'absolute';
shim.style.border = '0';
shim.style.filter = 'alpha(opacity=0)'; // IE-only transparency
shim.style.zIndex = '99'; // one below the menu
shim.frameBorder = '0';
// Size the shim to match the menu
shim.style.width = menuElement.offsetWidth + 'px';
shim.style.height = menuElement.offsetHeight + 'px';
// Insert before the menu in DOM
menuElement.parentNode.insertBefore(shim, menuElement);
menuElement.style.zIndex = '100';
return shim;
}
This was the ugly reality of DHTML development in 2001. Every clever technique had an IE-specific workaround.
Hit Counter and Page Statistics Using DHTML
Another DHTML technique from 2001: updating a visible page counter without reloading. This used document.write() inline - the precursor to Ajax, and equally crude:
<!-- Embed live visitor count from a CGI script -->
<!-- The script outputs JavaScript that calls document.write() -->
<script type="text/javascript" src="/cgi-bin/counter.pl?output=js"></script>
<!-- counter.pl outputs something like: -->
<!-- Content-Type: text/javascript -->
<!-- document.write("You are visitor number <b>14,847</b>"); -->
This technique - loading a <script> tag from an external URL that outputs JavaScript - was used for hit counters, ad networks, web rings, and third-party analytics. It is essentially the same mechanism as modern third-party tracking scripts. The pattern from 2001 became the foundation of the entire ad tech industry.
What DHTML Became
By 2005, jQuery had abstracted the browser incompatibilities that made DHTML development painful. The setTimeout approach to preventing premature menu close is still used in virtually every modern tooltip and dropdown component. The z-index stacking context problem was solved by CSS specification improvements in browsers circa 2007.
The iframe shim technique for <select> overlap disappeared when IE7 fixed the bug in 2006. The detection-and-branch pattern (if (window.event) vs if (e.target)) disappeared when IE9 adopted the W3C event model in 2011.
What remained: the conceptual model of JavaScript manipulating CSS properties and element visibility to create interactive behavior without server round-trips. That model is still what every modern UI framework - React, Vue, Angular - implements at its core. The dropdown menu of 2001 and a React popover of today are architecturally the same thing, separated by twenty years of tooling improvement.
Aunimeda builds production-grade backend systems - APIs, microservices, real-time applications, and system integrations.
Contact us for backend engineering services. See also: Custom Software Development, Web Development