Какой должна быть логика реализации выпадающего меню с возможностью перехода как на корневую страницу, так и на вложенные?

Имеется меню, где есть корневые страницы, и у некоторых из них - дочерние в виде выпадающего подменю:

.navbar {
  display: flex;
  list-style: none;
  padding: 0;
}
.navbar a:hover {
  opacity: .7;
}
.navbar > li a {
  padding: 15px;
}
.has-sub-menu {
  position: relative;
}
.sub-menu {
  padding: 0;
  position: absolute;
  white-space: nowrap;
  list-style: none;
  padding-top: 10px;
  opacity: 0;
  pointer-events: none;
}
.has-sub-menu a:hover + .sub-menu,
.sub-menu:hover {
  opacity: 1;
  pointer-events: auto;
}
<nav>
  <ul class="navbar">
    <li><a href="#">О компании</a></li>
    <li class="has-sub-menu">
      <a href="#">Проекты</a>
      <ul class="sub-menu">
        <li><a href="#">Проект А</a></li>
        <li><a href="#">Проект В</a></li>
        <li><a href="#">Проект С</a></li>
      </ul>
    </li>
    <li><a href="#">Контакты</a></li>
  </ul>
</nav>

Проблемы с ним две:

1. На десктопах выпадайки появляются при наведении, и пользователь может перейти на дочерние страницы. Но для перехода на корневую по пункту меню нужно кликнуть, что совершенно не очевидно.

2. На тачскринах, где ховера по сути нет, а есть только клик, пользователя сразу перебрасывает на корневую (при этом на долю секунды показывается, а потом скрывается выпадайка, что вообще выглядит как баг)

Как подружить между собой эти два варианта взаимодействия с меню? Возможно, одним css не обойтись, и нужен js? Не прошу за меня писать код, прошу помощи с логикой реализации.


Ответы (1 шт):

Автор решения: puffleeck

На десктопах выпадайки появляются при наведении, и пользователь может перейти на дочерние страницы. Но для перехода на корневую по пункту меню нужно кликнуть, что совершенно не очевидно.

раз уж кликабельность заголовка не очевидна, так пусть он и будет только лишь заголовком меню\триггером события. ссылку же спрятать внутрь меню.

На тачскринах, где ховера по сути нет, а есть только клик, пользователя сразу перебрасывает на корневую

есть и другие... как бы это правильней назвать... "состояния" пожалуй.

*:active, *:focus. второй из них прекрасно подходит для подобных целей

#abc {
  display: grid;
  grid-template: 4em / repeat(4, auto);
}
li:not(:first-child){
  display: none;
}

#w:active > *{
  display: list-item;
  background: skyblue;
  border: 5px solid blue;
}

#x:focus > *,
#y:focus > *{
  display: list-item;
  background: pink;
  border: 5px solid violet;
}

#z:hover > *{
  display: list-item;
  background: green;
  border: 5px solid lime;
}

ol{background: gray;}

ol > *{
border-radius: .5em;
padding: .2em;
}
<div id='abc'>
  <ol id='w'>
    <li>#w:active</li>
    <li>тригерится на зажатие мыши\долгий тап</li>
    <li>для такой задачи конечно бесполезен</li>
    <li>но для ознакомления не будет лишним</li>
  </ol>
  <ol id='x'>
    <li>#x:focus</li>
    <li>сам по себе фокус не применяется</li>
    <li>к чему то кроме инпутов, кнопок и т.п.</li>
  </ol>
  <ol id='y' tabindex='1'>
    <li>#y:focus + tabindex</li>
    <li>но если добавить контейнеру</li>
    <li><i>tabindex</i>, то уже всё норм</li>
    <li><button onclick='blur()'>закрыть можно вызвав blur()</button></li>
    <li>либо кликнув\тапнув мимо контейнера</li>
  </ol>
  <ol id='z'>
    <li>#z:hover</li>
    <li>ну и ховер для сравнения</li>
  </ol>
</div>

→ Ссылка