해당 Git Blog를 만들면서 어려웠던 부분, 정리하고 싶은 부분을 기록하기 위한 포스트입니다.
해당 포스팅에는 JavaScript와 정적 사이트 생성 프레임워크 중 하나인 Jekyll을 활용하여 Toggle Menu를 구현하는 과정이 설명되어있습니다.
1. Toggle Menu 필요성
이전에 작성한 ‘검색 엔진’ 포스팅 이후, 한달만에 작성하는 포스트입니다.
학교 기말고사와 기간이 겹쳐서 시간적 여유가 부족했던 이유도 있으나, 이전에 구현했던 Toggle Menu를 좀 더 제 입맞에 맞게 튜닝을 하고 포스팅을 하고 싶은 욕심도 있었습니다. 그래서 이제서야 Toggle Menu를 포스팅하게 되었습니다.
(튜닝하는 과정에서 코드를 아예 새로 작성을 했기 때문에 오히려 복습이 되는 이점이 있었습니다.)
블로그를 만들면서 Toggle Menu가 필요했던 이유는 모바일 환경에서 사용자에게 적절한 사용자 경험을 주고 싶었기 때문입니다.
PC 버전에서 제 블로그의 구성을 보면 상단에 카테고리가 존재하고 오른쪽에 검색 엔진이 위치하도록 설계했습니다.
하지만, 모바일 환경같은 작은 화면에서는 이를 모두 표현하는 것이 어렵습니다.
따라서 Toggle Menu의 필요성을 절감했고 이런 필요성을 따라가다 보니 Toggle Menu의 구현이 필요했습니다.
2. Toggle Menu 구성
이전에 말씀드린바와 같이 제가 Toggle Menu를 구현한 이유는 ‘PC 화면에서 존재하는 것들’을 모바일 환경에서 간략하게 표현하기 위함입니다.
따라서 제가 구상한 Toggle Menu의 구성은 아래와 같습니다.
1. 카테고리
2. 검색엔진
3. config.yml 구성
이 과정을 상세하게 설명드리기 위해 우선 제가 작성한 config.yml의 구성부터 소개하겠습니다.
제가 작성한 yml에서 navigation은 아래와 같은 구조를 따라갑니다.
navigation:
- title: XXX
url: ~~~
- title: YYY
url: ~~~~~
dropdown:
- title: Y-Dropdown1
url: ~~~~~
- title: Y-Dropdown2
url: ~~~~~
2dropdown:
- title: Y-2Dropdown
url: ~~~~~~
즉, 간략히 정리하면 ‘navigation - dropdown - 2dropdown’의 형식으로 구성되어있습니다.
4. HTML 구성
위의 yml파일을 바탕으로 liquid 문법을 사용하여 Toggle Menu에 사용할 navigation html을 작성합니다.
제가 원하는 동작은 아래와 같습니다.
1. Dropdown이 없는 카테고리이면 사용자가 클릭했을 때, 해당 카테고리로 바로 넘어간다.
2. Dropdown이 있는 카테고리 중에서 2Dropdown이 없는 카테고리면 사용자가 클릭했을 때, 해당 카테고리로 바로 넘어간다.
3. Dropdown이 있는 카테고리 중에서 2Dropdown이 있는 카테고리면 사용자가 Dropdown을 클릭했을 때, 아무런 리다이렉션이 없다.
4. 2Dropdown은 클릭하면 바로 리다이렉션이 된다.
간단하게 사진으로 이해해보면 아래와 같습니다.
붉은색 텍스트는 클릭시 해당 페이지로 넘어갑니다. 반면, 검정색 텍스트는 클릭해도 아무런 리다이렉션이 없습니다.
이러한 구성의 HTML을 작성하는 것은 liquid 문법을 이용하여 쉽게 작성할 수 있습니다.
... // Toggle Menu 아이콘 관련 Html
{% for navigation in site.navigation %} // config.yml에서 작성한 navigation을 하나씩 살펴봅니다.
{% unless navigation.dropdown %} // 현재 살펴보는 navigation에 dropdown이 없으면 url을 제공합니다.
<li><a href="{{ navigation.url | relative_url}}">{{ navigation.title }}</a></li>
{% else %} // 만약, 현재 살펴보는 navigation에 dropdown이 있으면 url을 제공하지 않습니다.
<li><a href="javascript:void(0);"> {{ navigation.title }}
{% for dropdown in navigation.dropdown %} // navigation의 dropdown을 루프합니다.
{% unless dropdown.2dropdown %} // 2dropdown이 존재하지 않으면 url을 제공합니다.
<li><a href="{{ dropdown.url | relative_url}}">{{ dropdown.title }}</a></li>
{% else %} // 만약 2dropdown이 존재하면 Title을 출력하고 루프를 합니다.
<a href="#"> {{ dropdown.title }}
{% for 2dropdown in dropdown.2dropdown %} // 2dropdown은 모두 url을 제공합니다.
<li><a href="{{ 2dropdown.url | relative_url}}">{{ 2dropdown.title }}</a></li>
...
... // Search Bar 관련 Html
5. Toggle Menu 아이콘 이벤트 추가
사용자가 Toggle Menu 아이콘을 클릭하면 Toggle Menu가 보여지도록 해야합니다.
클릭 이벤트의 작동을 어떻게 구현할지 고민해본 결과, 사용자가 클릭을하면 html class에 ‘active’를 추가하거나 삭제하는 방식으로 구현하기로 했습니다.
당연히 개발자는 active가 있을 때와 없을 때 사용자에게 어떻게 보여질지를 고민하여 css를 작성해야합니다.
주요 코드는 아래와 같습니다.
const toggle = document.getElementById('toggle-menu'); // 토글 메뉴 아이콘
const responsiveSidebar = document.querySelector('.responsive-nav'); // 토글 메뉴
toggle.addEventListener('click', function(event){
event.preventDefault(); // Prevent default link behavior
responsiveSidebar.classList.toggle('active');
});
즉, 토글 메뉴 아이콘에서 발생하는 click 이벤트를 확인하여 토글 메뉴에 ‘active’를 삭제하거나 추가하여 동작하도록 합니다.
6. Toggle Menu 이벤트 추가
위에서는 Toggle Menu 아이콘에 클릭 이벤트를 감지하여 ‘active’를 추가하거나 삭제하도록 작성했습니다.
마찬가지로, Toggle Menu에 존재하는 카테고리에서도 클릭 이벤트를 감지하여 하위 dropdown에 ‘active’를 추가해주거나 삭제하면 dropdown을 화면에 표시하거나 숨길 수 있습니다.
주요 코드는 아래와 같습니다.
document.addEventListener('DOMContentLoaded', () => {
// 클릭이 가능한 리스트를 모두 변수로 가져온다.
const toggle_can_click = document.querySelectorAll('.toggle-nav-base li');
// 위 변수들 중에서 dropdown이 존재한다면 클릭시 하위 드롭다운이 표시되도록 처리한다.
toggle_can_click.forEach(click_object => {
click_object.addEventListener('click', function(event) {
event.stopPropagation();
const child_drop = click_object.querySelector('.toggle-nav-dropdown');
const child_drop2 = click_object.querySelector('.toggle-nav-2dropdown');
if(child_drop !== null){
child_drop.classList.toggle('active');
}
else if(child_drop2 !== null){
child_drop2.classList.toggle('active');
};
if(click_object.classList.contains('active-dropdown')) {
const current_drop = click_object.querySelectorAll('.active-dropdown');
const current_droplist = click_object.querySelectorAll('[class*="active"]');
click_object.classList.remove('active-dropdown');
current_drop.forEach(e => {
e.classList.remove('active-dropdown');
})
current_droplist.forEach(e => {
e.classList.remove('active');
})
} else{
click_object.classList.add('active-dropdown');
}
});
});
});
위 코드는 HTML의 구성에 따라서 동작이 달라질 수 있습니다.
(만약, 해당 코드를 참고하면서 Toggle Menu를 구현한다면 코드 기준으로 child_drop과 child_drop2가 어떤 object를 의미하는지는 직접 확인해야합니다.)
6-1. Toggle Menu 이벤트 2
추가적으로 설명드릴 부분은 ‘active-dropdown’이라는 클래스를 이용했다는 것입니다.
그 이유는 아래와 같은 동작을 구현하기 위함입니다.
1. navigation을 선택하여 dropdown이 펼쳐진 상태 & 2dropdown이 존재하는 여러 dropdown을 동시에 펼쳐놓은 상태
2. 위 상황에서 펼쳐진 navigation을 클릭하면 모든 dropdown이 화면에서 사라짐
3. 하지만, 해당 navigation을 다시 클릭하면 이전에 펼쳐놓은 상태 그대로 사용자에게 다시 보여짐
-> 따라서, navigation을 접었다가 펼치면 이전에 추가된 active가 모두 사라져야함
해당 동작을 구현하기 위해 clic_object에 ‘active-dropdown’을 추가하거나 삭제하도록 합니다.
클릭 이벤트가 동작하는 시점에 active_dropdown이 존재한다면 하위 active가 포함된 클래스에서 active를 모두 제거합니다.
7. 마무리
제가 Toggle Menu를 구현할 때, 가장 중요한 아이디어는 ‘active’라는 클래스의 추가/삭제를 활용하여 클래스의 상태를 변경해주는 것이었습니다.
이처럼 동일한 클래스에 상태를 부여하는 방식을 사용하면 css를 통해 다양한 애니메이션을 만들 수 있습니다.
(저의 경우에는 메뉴가 슬라이딩하는 방식으로 구현해봤습니다.)
튜닝 이전의 코드는 카테고리의 url을 js에서 직접 설정하는 방식을 사용했는데, 이 과정 때문에 코드가 조금 번잡했습니다.
또한, 4번에서 소개해드린 동작 방식을 구현하는 과정에서 불필요한 조건이 계속 따라 붙었기때문에 코드를 다시 작성하기로 결정했었습니다.
아직 구현하진 못했으나, 다음에는 블로그에 댓글 작성 기능을 추가하려고 합니다. 관련 내용은 좀 더 찾아보고 완성한 이후 이어서 포스팅하도록 하겠습니다!