Topbar의 특정 메뉴에 마우스를 갖다대면 그 아래로 하위 메뉴들이 펼쳐지는 드롭다운 메뉴를 만들자. 이때 퍼포먼스를 위해 CSS를 활용하고 Javascript의 사용을 최소화 한다. 단, 드롭다운 메뉴가 펼쳐질때 애니메이션을 사용하지 않는다.

Requirement

  • HTML, CSS, Javascript

이 포스트의 Javascript에서는 JQuery를 사용한다. 물론 순수 Javascript를 써도 아주 간단한 수준으로만 사용되지만 요즘 JQuery를 금지할 정도로 퍼포먼스를 요하는 일은 없기 때문에 편의상 JQuery를 쓴다.

Sample 소개

이어서 2가지 형태의 Dropdown Menu들을 보자.

아래 샘플들을 보면 검은색 띠 형태로 Topbar가 있다. 그리고 그 안에는 ShortMenu, LooooooogMenu 2개의 Main Menu가 있다.
이 Main Menu들에 마우스를 가져다 대면 hover 이벤트가 발생하고 마우스가 위치한 Main Menu 아래로 Sub Menu 리스트가 펼쳐진다.
마지막으로 특정 Sub Menu에 마우스를 가져다대면 Sub Menu의 바탕색이 회색으로 변해서 사용자에게 현재 마우스가 위치한 곳을 명확히 알려준다.

1. 좌측 정렬 Dropdown Menu

이 Dropdown 샘플은 Topbar 안에 Main Menu가 좌측으로 정렬 되어있다. 그리고 Sub Menu가 길어져서 좌측 화면 밖으로 벗어나는 것을 막기 위해 Sub Menu도 부모 기준으로 좌측 정렬되어 있다.
즉, Sub Menu의 좌측 위치가 부모 메뉴의 좌측과 같은 위치에서 시작하고 Sub Menu의 내용이 길어지면 Sub Menu의 가로 길이가 좌에서 우로 확장된다.

Sample

구현 설명

코드가 길고 설명이 많지만 그 내용은 별로 어렵지 않으므로 글을 읽는 따분함만 좀 견뎌낸다면 금방 핵심을 이해하고 응용할 수 있을 것이다.
그리고 좌측 정렬 Dropdown Menu를 이해하면 2. 우측 정렬 Dropdown Menu는 몇가지 확인하는 것만으로 익힐 수 있다.

HTML 구현

Dropdown 메뉴의 HTML 구조는 아래와 같다. Topbar를 포함하는 전체 구조는 밑에서 전체 코드로 싣겠다.

HTML Structure of Dropdown Menu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<ul class='main-menus'>
  <li class='main-menu-item'>
    <a class='menu-title'>ShortMenu</a>
    <ul class='sub-menus'>
      <li class='sub-menu-item'><a class='menu-title'>Sub Menu</a></li>
      <li class='sub-menu-item'><a class='menu-title'>Looong Sub Menu</a></li>
    </ul>
  </li>
  <li class='main-menu-item'>
    <a class='menu-title'>LooooooogMenu</a>
    <ul class='sub-menus'>
      <li class='sub-menu-item'><a class='menu-title'>Sub Menu 1</a></li>
      <li class='sub-menu-item'><a class='menu-title'>Hello World !</a></li>
    </ul>
  </li>
</ul>

CSS 구현

그리고 아래와 같은 CSS를 적용한다. Dropdown 기능 구현을 위한 부분만 설명하고 나머지 CSS 설정은 밑에서 전체 코드로 싣도록 하겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.main-menu-item {
  position: relative;
}
.main-menu-item:hover {
  color: black;
  background-color: white;
}
.sub-menus {
  position: absolute;
  left: -9999px;
  margin-left: -1;
}
.main-menu-item:hover .sub-menus {
  left: 0;
}
.sub-menu-item:hover {
  background-color: #ccc;
}
  • Line 1~3: 메인 메뉴 요소에 대한 CSS
    여기에 position: relative를 설정해두는 이유는 하위 요소에 position: absolute를 부여했을때 그 요소의 offset 속성 top, left, right, bottom이 상위 요소 중 position: relative를 가지고 있는 요소를 기준으로 계산 되기 때문이다.

position: absolute 속성에 대해

position: absolute를 가진 요소의 offset top, right, bottom, left은 상위 요소 중 position: relative, absolute, fixed, sticky를 갖는 요소(position: static이 아닌 요소)를 만났을때 그 상위 요소(=컨테이닝 블록)를 기준으로 offset을 계산한다. 또 만약 그런 상위 요소가 없다면 <body>를 기준으로 offset을 계산한다.
이와 관련한 더 자세한 정보는 MDN Docs에서 확인할 수 있다.

  • Line 4~7
    마우스가 해당 요소에 hover 하면 흑백을 반전시킨다.

  • Line 8~9: 하위 메뉴에 대한 CSS
    position: absolute가 있으므로 이 요소는 부모 요소의 바깥으로 튀어 나갈수 있다. 또한 자신보다 상위 요소 중 position: relative 속성을 가진 요소를 기준으로 offset이 결정된다.

  • Line 10
    하위 메뉴는 평소에 보이지 않게 하기 위해 화면 왼쪽 바깥에 둔다. left: -9999px 또는 height: 0 속성으로도 같은 효과를 낼수 있는데 left: -9999px가 성능면에서 더 좋다고 한다.

  • Line 11
    하위 메뉴가 border 1px를 가지므로 그만큼 마이너스(-) margin을 주어서 위치를 조절한다.

  • Line 13~15
    마우스 hover가 일어나면 Line 10에서 설정했던 left: -9999pxleft: 0로 바꿔준다. 그러면 하위 메뉴의 left 위치 값이 0 위치로 이동한다.
    이때 Line 2의 position: relative에 의해 부모의 왼쪽 면이 left: 0에 해당하는 위치로 연산되고 하위 메뉴가 부모의 왼쪽 면으로 정렬된다.

  • Line 16~18
    하위 메뉴의 아이템에 마우스가 hover 하면 회색으로 바탕색을 변경해준다.

Javascript 구현

마지막으로 아래의 Javascript 코드를 실행한다.
이 코드는 Main Menu의 가로 길이와 Sub Menu의 가로 길이를 비교해서 Sub Menu의 가로 길이가 Main Menu의 가로 길이 보다 짧으면 최소 Main Menu 만큼의 길이가 되도록 수정 하는 기능을 수행한다. (Sub Menu의 가로 길이가 더 긴 경우는 아무것도 하지 않음.)

1
2
3
4
5
6
7
8
9
10
var $allMainMenuItem = $('.main-menu-item');
$allMainMenuItem.each(function(idx, elem){
  var $mainMenuItem = $(this);
  var $subMenus = $($mainMenuItem.children('.sub-menus'));
  var mainMenuWidth = $mainMenuItem.width();    // 메인 메뉴 가로 길이
  var subMenuWidth = $subMenus.width();         // 서브 메뉴 가로 길이
  if(mainMenuWidth > subMenuWidth) {            // 두 가로 길이 비교
    $subMenus.css('min-width', mainMenuWidth);  // 서브 메뉴 최소 가로 길이 수정
  }
});

Sample Code

아래는 Topbar에서부터 Dropdown Menu를 구현하는 전체 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
<html>
<head>
<style>
/* Dropdown 설정 */
.main-menu-item {
  position: relative;
}
.main-menu-item:hover {
  color: black;
  background-color: white;
}
.sub-menus {
  position: absolute;
  left: -9999px;
  margin-left: -1;
}
.main-menu-item:hover .sub-menus {
  left: 0;
}
.sub-menu-item:hover {
  background-color: #ccc;
}

/* 기타 설정: 모양과 요소 정렬 등 */
body {
  padding: 0;
  margin: 0;
  user-select: none;
}
ul, li {
  margin-block: 0;
  margin-inline: 0;
  padding-inline: 0;
  list-style: none;
}
a {
  cursor: pointer;
}
.topbar {
  top: 0;
  background: black;
  color: white;
  width: 100%;
  height: 32px;
  font-size: 20px;
}
.topbar-wrapper {
  display: flex;
  align-items: flex-end;
  height: 100%;
  padding: 0 10px 0;
}
.main-menus {
  display: flex;
}
.main-menu-item {
  white-space: nowrap;
  cursor: pointer;
}
.menu-title {
  padding: 0 10px 0;
}
.sub-menus {
  border-width: 0 1px 1px 1px;
  border-style: solid;
  border-color: black;
  background-color: white;
  color: black;
}
.sub-menu-item {
  white-space: nowrap;
  cursor: pointer;
}
.content {
  font-size: 16px;
  color: black;
  padding-left: 10px;
  padding-right: 10px;
}
.header {
  font-size: 20px;
  margin-bottom: 5px;
  border-bottom: 1px solid gray;
}
</style>
</head>
<body>
<div class='topbar'>
  <div class='topbar-wrapper'>
    <ul class='main-menus'>
      <li class='main-menu-item'>
        <a class='menu-title'>ShortMenu</a>
        <ul class='sub-menus'>
          <li class='sub-menu-item'><a class='menu-title'>Sub Menu</a></li>
          <li class='sub-menu-item'><a class='menu-title'>Looong Sub Menu</a></li>
        </ul>
      </li>
      <li class='main-menu-item'>
        <a class='menu-title'>LooooooogMenu</a>
        <ul class='sub-menus'>
          <li class='sub-menu-item'><a class='menu-title'>Sub Menu 1</a></li>
          <li class='sub-menu-item'><a class='menu-title'>Hello World !</a></li>
        </ul>
      </li>
    </ul>
  </div>
</div>
<div class='content'>
  <h1 class="header" id="섹션-1">섹션 1</h1>
  <p>
  본문입니다. 본문입니다. 본문입니다. 본문입니다.
  본문입니다. 본문입니다. 본문입니다. 본문입니다.
  본문입니다. 본문입니다. 본문입니다. 본문입니다.
  본문입니다. 본문입니다. 본문입니다. 본문입니다.
  본문입니다. 본문입니다. 본문입니다. 본문입니다.
  본문입니다. 본문입니다. 본문입니다. 본문입니다.
  본문입니다. 본문입니다. 본문입니다. 본문입니다.</p>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(function(){
  // for dropdown
  var $allMainMenuItem = $('.main-menu-item');
  $allMainMenuItem.each(function(idx, elem){
    var $mainMenuItem = $(this);
    var $subMenus = $($mainMenuItem.children('.sub-menus'));
    var mainMenuWidth = $mainMenuItem.width();
    var subMenuWidth = $subMenus.width();
    if(mainMenuWidth > subMenuWidth) {
      $subMenus.css('min-width', mainMenuWidth);
    }
  });
});
</script>
</body>
</html>

2. 우측 정렬 Dropdown Menu

이 Dropdown 샘플은 1. 좌측 정렬 Dropdown Menu와 반대로 Main Menu가 우측 정렬 되어 있다. 그리고 Sub Menu가 길어져서 우측 화면 밖으로 벗어나는 것을 막기 위해 Sub Menu도 부모 기준으로 우측 정렬되어 있다.
즉, Sub Menu의 우측 위치가 부모 메뉴의 우측과 같은 위치에서 시작하고 Sub Menu의 내용이 길어지면 Sub Menu의 가로 길이가 우에서 좌로 확장된다.

Sample

구현 설명

우측 정렬 Dropdown Menu는 1. 좌측 정렬 Dropdown Menu에서 CSS만 3가지 수정하면 구현할 수 있다.

사실 3가지 CSS 수정 사항중 Dropdown에 직접적인 영향을 주는 사항은 한가지뿐이다.

  • 첫번째
    Dropdown에 직접적 영향을 주는 한가지
1
2
3
4
.main-menu-item:hover .sub-menus {
  left: auto;   // -9999px 해제
  right: 0;     // 오른쪽 정렬
}

Sub Menu는 원래 left: -9999px 속성을 가지고 화면 밖으로 나가 있다. 그리고 마우스가 Main Menu에 hover하면 Sub Menu가 화면의 특정 위치(이하 offset)로 돌아온다.

좌측 정렬 Dropdown Menu에서는 Sub Menu의 좌측 정렬을 위해 offset 값으로 left: 0를 줬었다.

우측 정렬에서는 Sub Menu가 부모 요소 기준 오른쪽 편에 정렬하도록 right: 0를 주면 된다. 그리고 left: auto를 추가해서 -9999px 값을 해제시키자.

  • 두번째
    Sub Menu 간격 미세 조정
1
2
3
4
5
.sub-menus {
  position: absolute;
  left: -9999px;
  margin-right: -1;  // 간격 미세 조정값
}

좌측 정렬 Dropdown Menu에서 Border가 1px를 차지하므로 margin-left: -1px를 부여했었다. 비슷하지만 이번엔 오른쪽으로 margin을 부여한다.

  • 세번째
    Main Menu 우측 정렬
1
2
3
4
5
6
7
.topbar-wrapper {
  display: flex;
  align-items: flex-end;
  justify-content: flex-end;  // 우측 정렬
  height: 100%;
  padding: 0 10px 0;
}

이러한 우측 정렬 설정은 Main Menu의 상위 요소가 display: flex로 설정되었을 때 사용 할 수 있는 방법이다.

Sample Code

아래는 Sample을 구현하는 전체 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
<html>
<head>
<style>
/* Dropdown 설정 */
.main-menu-item {
  position: relative;
}
.main-menu-item:hover {
  color: black;
  background-color: white;
}
.sub-menus {
  position: absolute;
  left: -9999px;
  margin-right: -1;
}
.main-menu-item:hover .sub-menus {
  left: auto;
  right: 0;
}
.sub-menu-item:hover {
  background-color: #ccc;
}

/* 기타 설정: 모양과 요소 정렬 등 */
body {
  padding: 0;
  margin: 0;
  user-select: none;
}
ul, li {
  margin-block: 0;
  margin-inline: 0;
  padding-inline: 0;
  list-style: none;
}
a {
  cursor: pointer;
}
.topbar {
  top: 0;
  background: black;
  color: white;
  width: 100%;
  height: 32px;
  font-size: 20px;
}
.topbar-wrapper {
  display: flex;
  align-items: flex-end;
  justify-content: flex-end;
  height: 100%;
  padding: 0 10px 0;
}
.main-menus {
  display: flex;
}
.main-menu-item {
  white-space: nowrap;
  cursor: pointer;
}
.menu-title {
  padding: 0 10px 0;
}
.sub-menus {
  border-width: 0 1px 1px 1px;
  border-style: solid;
  border-color: black;
  background-color: white;
  color: black;
}
.sub-menu-item {
  white-space: nowrap;
  cursor: pointer;
}
.content {
  font-size: 16px;
  color: black;
  padding-left: 10px;
  padding-right: 10px;
}
.header {
  font-size: 20px;
  margin-bottom: 5px;
  border-bottom: 1px solid gray;
}
</style>
</head>
<body>
<div class='topbar'>
  <div class='topbar-wrapper'>
    <ul class='main-menus'>
      <li class='main-menu-item'>
        <a class='menu-title'>ShortMenu</a>
        <ul class='sub-menus'>
          <li class='sub-menu-item'><a class='menu-title'>Sub Menu</a></li>
          <li class='sub-menu-item'><a class='menu-title'>Looong Sub Menu</a></li>
        </ul>
      </li>
      <li class='main-menu-item'>
        <a class='menu-title'>LooooooogMenu</a>
        <ul class='sub-menus'>
          <li class='sub-menu-item'><a class='menu-title'>Sub Menu 1</a></li>
          <li class='sub-menu-item'><a class='menu-title'>Hello World !</a></li>
        </ul>
      </li>
    </ul>
  </div>
</div>
<div class='content'>
  <h1 class="header" id="섹션-1">섹션 1</h1>
  <p>
  본문입니다. 본문입니다. 본문입니다. 본문입니다.
  본문입니다. 본문입니다. 본문입니다. 본문입니다.
  본문입니다. 본문입니다. 본문입니다. 본문입니다.
  본문입니다. 본문입니다. 본문입니다. 본문입니다.
  본문입니다. 본문입니다. 본문입니다. 본문입니다.
  본문입니다. 본문입니다. 본문입니다. 본문입니다.
  본문입니다. 본문입니다. 본문입니다. 본문입니다.</p>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(function(){
  // for dropdown
  var $allMainMenuItem = $('.main-menu-item');
  $allMainMenuItem.each(function(idx, elem){
    var $mainMenuItem = $(this);
    var $subMenus = $($mainMenuItem.children('.sub-menus'));
    var mainMenuWidth = $mainMenuItem.width();
    var subMenuWidth = $subMenus.width();
    if(mainMenuWidth > subMenuWidth) {
      $subMenus.css('min-width', mainMenuWidth);
    }
  });
});
</script>
</body>
</html>

Meta Info

Categories: ,

Published At:

Modified At:

Leave a comment