О насБлогКонтакты
Веб-разработка5 июля 2001 г. 6 мин 96Обновлено: 22 июня 2026 г.

DHTML: выпадающие меню и динамические эффекты на JavaScript (2001)

AunimedaAunimeda
📋 Содержание

К 2001 году JavaScript стал достаточно мощным, чтобы в реальном времени менять HTML-элементы - их стили, позиции и видимость, без перезагрузки страницы. Технология получила название DHTML, или Dynamic HTML. Это был не новый язык и не спецификация - это был маркетинговый термин для комбинации JavaScript, CSS и Document Object Model, используемой для создания интерактивных эффектов.

Главное применение DHTML в 2001 году - выпадающее навигационное меню. Каждый корпоративный сайт хотел такое. Наведи на «Продукты» - появляется подменю с подкатегориями. Наведи на «Услуги» - другое подменю. Без перезагрузки. Без Flash. Чистый HTML, CSS и JavaScript.

Заставить это работать одновременно в Internet Explorer 5 и Netscape 6 - настоящий инженерный вызов.


Проблема совместимости браузеров

IE5 и Netscape 6 оба поддерживали W3C DOM (document.getElementById, element.style), но расходились в модели событий. Netscape 4 с его document.layers мы игнорируем - к 2001 году его доля упала настолько, что это считалось допустимым ограничением.

// IE5: onmouseover/onmouseout срабатывали на элементе И на всех дочерних
// Netscape 6: та же логика - W3C-модель всплытия
// Но свойства объекта события различались:

// IE5:
element.onmouseover = function() {
    var target        = window.event.srcElement;   // только IE
    var relatedTarget = window.event.fromElement;
};

// Netscape 6 / W3C:
element.onmouseover = function(e) {
    var target        = e.target;
    var relatedTarget = e.relatedTarget;
};

// Кросс-браузерные обёртки - были у всех:
function getEvent(e)         { return e || window.event; }
function getTarget(e)        { return e.target || e.srcElement; }
function getRelatedTarget(e) { return e.relatedTarget || e.fromElement; }

Полная реализация выпадающего меню

Подход: абсолютно позиционированные <div> скрыты по умолчанию, показываются при onmouseover, скрываются при onmouseout. Сложность: при движении мыши из родительской ссылки в подменю срабатывал onmouseout на родителе и закрывал меню до того, как пользователь успевал кликнуть.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>DHTML-навигация - 2001</title>
<style type="text/css">
body, td, th { font-family: Arial, Helvetica, sans-serif; font-size: 13px; }

#navbar {
    width: 100%;
    background-color: #336699;
    height: 28px;
    position: relative;
}

.nav-item {
    position: relative;
    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; }

.submenu {
    position: absolute;
    top: 28px;
    left: 0;
    width: 170px;
    background-color: #f5f5f5;
    border: 1px solid #336699;
    border-top: 2px solid #336699;
    display: none;
    z-index: 100;
}

.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">
  <div class="nav-item" id="nav-products">
    <a href="products.html"
       onmouseover="showMenu('menu-products')"
       onmouseout="scheduleHide('menu-products')">
      Продукты &#9660;
    </a>
    <div class="submenu" id="menu-products"
         onmouseover="cancelHide('menu-products')"
         onmouseout="scheduleHide('menu-products')">
      <a href="products/software.html">Программное обеспечение</a>
      <a href="products/hardware.html">Оборудование</a>
      <a href="products/services.html">Сервис</a>
    </div>
  </div>

  <div class="nav-item" id="nav-about">
    <a href="about.html"
       onmouseover="showMenu('menu-about')"
       onmouseout="scheduleHide('menu-about')">
      О компании &#9660;
    </a>
    <div class="submenu" id="menu-about"
         onmouseover="cancelHide('menu-about')"
         onmouseout="scheduleHide('menu-about')">
      <a href="about/company.html">О нас</a>
      <a href="about/team.html">Команда</a>
      <a href="about/careers.html">Вакансии</a>
    </div>
  </div>

  <div class="nav-item" id="nav-contact">
    <a href="contact.html"
       onmouseover="showMenu('menu-contact')"
       onmouseout="scheduleHide('menu-contact')">
      Контакты &#9660;
    </a>
    <div class="submenu" id="menu-contact"
         onmouseover="cancelHide('menu-contact')"
         onmouseout="scheduleHide('menu-contact')">
      <a href="contact/office.html">Наш офис</a>
      <a href="contact/form.html">Написать нам</a>
    </div>
  </div>
</div>

<script type="text/javascript">
// DHTML-меню - стиль 2001 года
// Требования: IE5+, Netscape 6+, Opera 5+

var hideTimers = {};

function showMenu(menuId) {
    hideAllMenus();
    var menu = document.getElementById(menuId);
    if (menu) {
        menu.style.display = 'block';
    }
    cancelHide(menuId);
}

function scheduleHide(menuId) {
    if (hideTimers[menuId]) {
        clearTimeout(hideTimers[menuId]);
    }
    // 300 мс - достаточно, чтобы переместить мышь из ссылки в подменю
    hideTimers[menuId] = setTimeout(function() {
        hideMenu(menuId);
    }, 300);
}

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 divs = document.getElementsByTagName('div');
    for (var i = 0; i < divs.length; i++) {
        if (divs[i].className === 'submenu') {
            divs[i].style.display = 'none';
        }
    }
}

document.onclick = function() { hideAllMenus(); };
</script>

</body>
</html>

Трюк с setTimeout

Самая важная техника в этой реализации - задержка скрытия на 300 мс. Без неё:

  1. Пользователь наводит на «Продукты» → подменю появляется
  2. Пользователь ведёт мышь от ссылки к подменю
  3. Срабатывает onmouseout на ссылке немедленно
  4. Подменю исчезает раньше, чем мышь туда добралась

Решение: не скрывать сразу. Запустить setTimeout. Когда мышь входит в подменю - вызвать cancelHide, обнулив таймер. Подменю остаётся открытым, пока мышь где-то внутри.

// Почему именно 300 мс? Эмпирическое тестирование в 2001 году:
// 100 мс - слишком быстро, медленные мыши и трекболы не успевали
// 500 мс - меню "прилипало", не закрывалось когда надо
// 300 мс - золотая середина на железе 2001 года
// Современные библиотеки тултипов и дропдаунов используют тот же диапазон.

Война z-index: проблема с select в IE5

В 2001 году z-index работал непредсказуемо в разных браузерах. У IE5 был печально известный баг: элементы <select> (выпадающие списки HTML) всегда отображались поверх абсолютно позиционированных элементов, независимо от z-index. Ваше красивое DHTML-меню уходило под <select> на странице.

Решение: разместить <iframe> позади меню, размером как меню. Iframe создавал новый stacking context, перекрывая <select>.

// IE5/IE6 "iframe shim" - обязателен на страницах с <select> рядом с навбаром

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
    shim.style.zIndex    = '99';

    shim.style.width  = menuElement.offsetWidth  + 'px';
    shim.style.height = menuElement.offsetHeight + 'px';

    menuElement.parentNode.insertBefore(shim, menuElement);
    menuElement.style.zIndex = '100';

    return shim;
}

Такова была реальность DHTML-разработки в 2001 году. За каждым изящным приёмом прятался IE-специфичный костыль.


Счётчик посещений через document.write

Ещё одна DHTML-техника того времени - «живой» счётчик посетителей без перезагрузки. Использовался document.write() прямо в HTML - предшественник Ajax, не менее примитивный:

<!-- Загрузка счётчика из CGI-скрипта -->
<!-- Скрипт выводит JavaScript, вызывающий document.write() -->
<script type="text/javascript" src="/cgi-bin/counter.pl?output=js"></script>

<!-- counter.pl выводит примерно такое: -->
<!--   Content-Type: text/javascript                                    -->
<!--   document.write("Вы посетитель номер <b>14 847</b>");             -->

Загрузка тега <script> с внешнего URL, выводящего JavaScript - именно так работали счётчики посещений, рекламные сети, веб-ринги и сторонняя аналитика. Паттерн из 2001 года стал основой всей индустрии рекламных технологий.


Чем стал DHTML

К 2005 году jQuery абстрагировал несовместимости браузеров, делавшие DHTML мучением. Подход с setTimeout для предотвращения преждевременного закрытия меню используется в каждой современной библиотеке тултипов и дропдаунов. Проблема z-index со <select> исчезла с улучшением CSS-спецификаций в браузерах примерно в 2007 году.

«Iframe shim» для перекрытия <select> ушёл в прошлое, когда IE7 исправил этот баг в 2006 году. Паттерн if (window.event) vs if (e.target) исчез, когда IE9 принял W3C-модель событий в 2011 году.

Что осталось: концептуальная модель - JavaScript манипулирует CSS-свойствами и видимостью элементов для создания интерактивного поведения без обращений к серверу. Именно эту модель реализует каждый современный UI-фреймворк - React, Vue, Angular. Выпадающее меню 2001 года и React Popover сегодня архитектурно одно и то же, разделённые двадцатью годами инструментального прогресса.

Читайте также

State of JavaScript 2026: что изменилось и куда движется экосистемаaunimeda
Веб-разработка

State of JavaScript 2026: что изменилось и куда движется экосистема

Vite обошёл webpack. TypeScript - дефолт для новых проектов. React сохраняет доминирование, но Signal-based фреймворки растут. AI-assisted coding меняет что значит 'написать код'. Честный разбор состояния JavaScript-экосистемы в 2026.

Google Chrome и V8: когда JavaScript стал быстрым (2008)aunimeda
Веб-разработка

Google Chrome и V8: когда JavaScript стал быстрым (2008)

2 сентября 2008 года Google выпустил Chrome. Не браузер - манифест. Внутри был V8: JIT-компилятор JavaScript, написанный с нуля. SunSpider показал, что V8 в 10 раз быстрее Firefox 3. За один день ожидания от JavaScript радикально изменились. Начался browser war 2.0.

Ajax: имя, изменившее веб (2005)aunimeda
Веб-разработка

Ajax: имя, изменившее веб (2005)

18 февраля 2005 года Джесси Джеймс Гаррет опубликовал статью «Ajax: новый подход к веб-приложениям». Технология не была новой - XMLHttpRequest существовал с 1999 года. Но имя изменило всё: разработчики получили паттерн, словарь и разрешение думать о браузере как о платформе приложений.

Нужна IT-разработка для вашего бизнеса?

Разрабатываем сайты, мобильные приложения и AI-решения для бизнеса в России. Бесплатная консультация.

Разработка сайтов

Получить консультацию Все статьи