О насБлогКонтакты
Технологии18 сентября 2001 г. 6 мин 99Обновлено: 18 мая 2026 г.

DHTML-навигация в Бишкеке 2001 года: выпадающие меню на чистом JavaScript

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

DHTML-навигация в Бишкеке 2001 года: выпадающие меню на чистом JavaScript

К 2001 году в бишкекских веб-студиях началась конкуренция. Клиенты уже знали, что у конкурента сайт с «красивым меню, которое само открывается». DHTML-навигация стала маркером профессионализма.

Реализовать её самостоятельно, без Flash, без сторонних библиотек - исключительно на JavaScript и CSS - значило знать платформу на глубоком уровне. Статьи Дмитрия Котерова на javascript.ru, книга «Dynamic HTML» Дэнни Гудмана, эксперименты в IE5 и Netscape 6. Так это делалось в Бишкеке в 2001 году.


Полная реализация: HTML + CSS + JavaScript

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Навигация DHTML - Бишкек, 2001</title>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
<style type="text/css">

* { margin:0; padding:0; }
body {
    font-family: Arial, Helvetica, sans-serif;
    font-size: 13px;
    background: #EEEEEE;
}

/* Навигационная полоса */
#topnav {
    background-color: #003399;
    height: 32px;
    position: relative;   /* обязательно для дочерних absolute */
    width: 100%;
}

/* Один пункт меню верхнего уровня */
.ni {
    position: relative;
    float: left;
    height: 32px;
}

.ni > a {
    display: block;
    line-height: 32px;
    padding: 0 16px;
    color: #FFFFFF;
    text-decoration: none;
    font-weight: bold;
    font-size: 12px;
    white-space: nowrap;
}

.ni > a:hover,
.ni > a.active { background-color: #0044CC; }

/* Выпадающее подменю */
.drop {
    display: none;           /* скрыто по умолчанию */
    position: absolute;
    top: 32px;               /* высота navbar */
    left: 0;
    min-width: 175px;
    background: #FFFFFF;
    border: 1px solid #003399;
    border-top: 3px solid #003399;
    z-index: 500;
    /* box-shadow в IE5 не работал - обходились без него */
}

.drop a {
    display: block;
    padding: 7px 12px;
    color: #222222;
    text-decoration: none;
    font-size: 12px;
    border-bottom: 1px dotted #DDDDDD;
}

.drop a:hover {
    background: #E8EEFF;
    color: #003399;
}

/* Основной контент под навбаром */
.page-body {
    padding: 20px;
    background: #FFFFFF;
    min-height: 400px;
}

</style>
</head>
<body>

<div id="topnav">

  <div class="ni" id="ni-company">
    <a href="company.asp"
       onmouseover="ndOpen('drop-company','ni-company')"
       onmouseout="ndClose('drop-company')">
      О компании &#9660;
    </a>
    <div class="drop" id="drop-company"
         onmouseover="ndKeep('drop-company')"
         onmouseout="ndClose('drop-company')">
      <a href="company/about.asp">О нас</a>
      <a href="company/history.asp">История</a>
      <a href="company/team.asp">Команда</a>
      <a href="company/licenses.asp">Лицензии</a>
    </div>
  </div>

  <div class="ni" id="ni-products">
    <a href="products.asp"
       onmouseover="ndOpen('drop-products','ni-products')"
       onmouseout="ndClose('drop-products')">
      Продукция &#9660;
    </a>
    <div class="drop" id="drop-products"
         onmouseover="ndKeep('drop-products')"
         onmouseout="ndClose('drop-products')">
      <a href="products/computers.asp">Компьютеры</a>
      <a href="products/periphery.asp">Периферия</a>
      <a href="products/network.asp">Сетевое оборудование</a>
      <a href="products/software.asp">Программное обеспечение</a>
    </div>
  </div>

  <div class="ni" id="ni-support">
    <a href="support.asp"
       onmouseover="ndOpen('drop-support','ni-support')"
       onmouseout="ndClose('drop-support')">
      Поддержка &#9660;
    </a>
    <div class="drop" id="drop-support"
         onmouseover="ndKeep('drop-support')"
         onmouseout="ndClose('drop-support')">
      <a href="support/faq.asp">Часто задаваемые вопросы</a>
      <a href="support/drivers.asp">Драйверы</a>
      <a href="support/contacts.asp">Контакты поддержки</a>
    </div>
  </div>

  <div class="ni" id="ni-contacts">
    <a href="contacts.asp"
       onmouseover="ndOpen('drop-contacts','ni-contacts')"
       onmouseout="ndClose('drop-contacts')">
      Контакты &#9660;
    </a>
    <div class="drop" id="drop-contacts"
         onmouseover="ndKeep('drop-contacts')"
         onmouseout="ndClose('drop-contacts')">
      <a href="contacts/bishkek.asp">Бишкек (главный офис)</a>
      <a href="contacts/osh.asp">Ош</a>
      <a href="contacts/form.asp">Написать нам</a>
    </div>
  </div>

</div><!-- /topnav -->

<div class="page-body">
  <h2>Добро пожаловать</h2>
  <p>Наведите курсор на пункты меню.</p>
  <br>
  <!-- Этот select тестировал баг IE5 с z-index: -->
  <select style="width:200px;">
    <option>Выберите город</option>
    <option>Бишкек</option>
    <option>Ош</option>
    <option>Джалал-Абад</option>
  </select>
</div>

<script type="text/javascript">
// DHTML-меню, Бишкек 2001 год
// Совместимость: IE 5.0+, Netscape 6.1+

var _timers  = {};   // таймеры закрытия
var _shims   = {};   // iframe-шимы для IE5

// Открыть подменю
function ndOpen(dropId, navId) {
    ndCloseAll();

    var drop = document.getElementById(dropId);
    if (!drop) return;
    drop.style.display = 'block';

    // Добавить класс active на ссылку в родителе
    var ni = document.getElementById(navId);
    if (ni) {
        var a = ni.getElementsByTagName('a')[0];
        if (a) a.className = 'active';
    }

    // IE5 z-index fix: iframe под меню, чтобы не скрывалось за <select>
    ndCreateShim(drop, dropId);

    ndCancelClose(dropId);
}

// Мышь вошла в подменю - не закрывать
function ndKeep(dropId) {
    ndCancelClose(dropId);
}

// Запланировать закрытие
function ndClose(dropId) {
    ndCancelClose(dropId);
    _timers[dropId] = setTimeout(function() {
        ndHide(dropId);
    }, 300);
    // 300 мс - проверено на офисных мышах в Бишкеке.
    // На трекболах советских времён требовалось чуть больше.
}

function ndCancelClose(dropId) {
    if (_timers[dropId]) {
        clearTimeout(_timers[dropId]);
        _timers[dropId] = null;
    }
}

function ndHide(dropId) {
    var drop = document.getElementById(dropId);
    if (drop) drop.style.display = 'none';

    // Убрать класс active со всех ссылок навбара
    var nis = document.getElementById('topnav').getElementsByTagName('a');
    for (var i = 0; i < nis.length; i++) {
        if (nis[i].className === 'active') nis[i].className = '';
    }

    // Скрыть шим
    if (_shims[dropId]) _shims[dropId].style.display = 'none';
    _timers[dropId] = null;
}

function ndCloseAll() {
    var drops = document.getElementById('topnav').getElementsByTagName('div');
    for (var i = 0; i < drops.length; i++) {
        if (drops[i].className === 'drop') {
            drops[i].style.display = 'none';
        }
    }
    // Снять все active-классы
    var as = document.getElementById('topnav').getElementsByTagName('a');
    for (var j = 0; j < as.length; j++) {
        if (as[j].className === 'active') as[j].className = '';
    }
}

// -------------------------------------------------------
// IE5 iframe shim - подменю иначе уходит ПОД <select>
// Netscape 6 это не нужно, поэтому проверяем document.all

function ndCreateShim(dropEl, dropId) {
    // Шим нужен только для IE (document.all - IE-специфика)
    if (!document.all) return;

    var shimId = 'shim_' + dropId;

    if (!_shims[dropId]) {
        var shim = document.createElement('iframe');
        shim.id          = shimId;
        shim.src         = 'about:blank';
        shim.scrolling   = 'no';
        shim.frameBorder = '0';

        shim.style.position = 'absolute';
        shim.style.border   = 'none';
        shim.style.zIndex   = '499';   // на 1 ниже меню (500)
        shim.style.left     = dropEl.style.left || '0px';
        shim.style.top      = '32px';
        shim.style.width    = '175px';
        shim.style.height   = '200px';  // с запасом

        // Прозрачность в IE через filter
        shim.style.filter = 'alpha(opacity=0)';

        dropEl.parentNode.insertBefore(shim, dropEl);
        _shims[dropId] = shim;
    } else {
        _shims[dropId].style.display = 'block';
    }
}

// Закрыть всё при клике вне меню
document.onclick = function(e) {
    var t = e ? e.target : window.event.srcElement;
    // Если клик не внутри #topnav - закрыть всё
    var inNav = false;
    while (t) {
        if (t.id === 'topnav') { inNav = true; break; }
        t = t.parentNode;
    }
    if (!inNav) ndCloseAll();
};
</script>

</body>
</html>

Типичные проблемы, с которыми сталкивались в Бишкеке

float + position в IE5. В IE5 дочерний position: absolute внутри float: left иногда смещался не туда. Лечилось добавлением position: relative на родительский элемент:

/* Без этого IE5 позиционировал .drop относительно body, а не .ni */
.ni { position: relative; }  /* обязательно! */

Кириллица в onmouseover. Если кириллица в JavaScript-строках была в другой кодировке чем страница - в статусной строке браузера появлялись крокозябры:

<!-- Кодировка страницы и JS должна совпадать -->
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
<script type="text/javascript">
// Эта строка должна быть в том же файле и той же кодировке:
var menuHint = 'Информация о компании';  // windows-1251 - работает
</script>

Внешний .js файл. В 2001 году часть разработчиков выносила JS в отдельный файл nav.js. Это создавало проблему кодировки в IE5: браузер мог неправильно определить кодировку JS-файла. Решение - либо держать JS прямо в HTML, либо указывать charset в теге script:

<script type="text/javascript" src="nav.js" charset="windows-1251"></script>

Наследие этих 300 миллисекунд

Задержка в 300 мс перед закрытием меню появилась в бишкекском коде в 2001 году как результат тестирования на реальных пользователях с офисными мышами и трекболами. Это не была выдумка - это была измеренная потребность.

Двадцать пять лет спустя библиотеки вроде Radix UI, Headless UI, Floating UI используют тот же диапазон: 150-300 мс. Потому что физиология движения руки не изменилась. Только мышь стала точнее, и нижнюю границу чуть сдвинули.

DHTML 2001 года - это React Popover 2026 года, разделённые инструментарием. Принцип один: показать элемент при входе курсора, дать достаточно времени добраться до него, скрыть при уходе.

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

Первые сайты на PHP3 в 2002: как мы верстали до CSS, без фреймворков и без Stack Overflowaunimeda
Технологии

Первые сайты на PHP3 в 2002: как мы верстали до CSS, без фреймворков и без Stack Overflow

В 2002 году не было CSS Zen Garden, не было jQuery, не было документации на русском. Был PHP 3/4, были таблицы для вёрстки, был Dreamweaver с WYSIWYG, и было сообщество на форуме phpclub.ru. Вот как выглядела реальная веб-разработка 20 лет назад.

Classic ASP и первые корпоративные веб-системы Бишкека (2000)aunimeda
Технологии

Classic ASP и первые корпоративные веб-системы Бишкека (2000)

В 2000 году первые бишкекские компании задумались о системах онлайн-авторизации: закрытые разделы сайта, личные кабинеты для клиентов, защищённые прайс-листы. Те, у кого были Windows-серверы, выбирали Classic ASP. Полный код системы входа, реалии разработки в Кыргызстане 2000 года.

Java-апплеты: интерактивный веб в Бишкеке 1999 годаaunimeda
Технологии

Java-апплеты: интерактивный веб в Бишкеке 1999 года

В 1999 году Java-апплеты обещали революцию: настоящие приложения прямо в браузере. На dial-up 33 кбит/с с задержкой 200-400 мс до европейских серверов - загрузка JVM занимала 30-60 секунд. Бишкекские разработчики всё равно их писали. Код, опыт, провал.

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

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

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