슬라이딩 모바일 메뉴 구현
반응형 웹에서 모바일 UI를 구현할 때는 화면 크기의 제약으로 인해 화면 위나 옆에서 메뉴가 펼쳐지면서 보이도록 구현합니다.
좁은 모바일 화면을 최대한 활용하는 방법이기 때문에 주로 이 방법을 사용하며, 기본 화면에는 메뉴를 열기 위한 아이콘만 표시합니다.
자바스크립트 라이브러리를 사용하면 메뉴를 펼쳤다 접는 과정의 코드를 단순화 할 수 있기 때문에 라이브러리를 활용하는 것도 좋은 방법중이 하나입니다.
여기서는 약간 번거로울 수 있지만, 자바스크립트만으로 슬라이딩 모바일 메뉴를 구현해봅니다.
모바일 메뉴 슬라이드는 왼쪽에서 오른쪽으로 펼쳐지는 것으로 하기로 합니다.
모바일 메뉴는 보이는 상태로 메뉴 너비만큼 왼쪽 화면 밖 위치에 있다가, 오른쪽 방향으로 움직이면서 표시가 됩니다.
메뉴가 사라질 때는 반대로 움직이면서 사라지게 됩니다.
완성된 소스는 다음 링크를 클릭해서 다운로드할 수 있습니다.
HTML 및 CSS 생성
HTML 코드는 모바일 메뉴를 호출하는 아이콘 버튼 하나와 감추어진 사이드 메뉴가 있습니다.
<button type="button" class="mobile-menu"><i class="fas fa-bars"></i></button>
<div class="menuwrap"> <nav id="menu"> <!-- "메뉴목록 표시" --> <ul class="category_list"> <li class=""><a class="link_sub_item" href="/category/1">기초</a></li> <li class=""><a class="link_sub_item" href="/category/2">실전</a></li> <li class=""><a class="link_sub_item" href="/category/3">코드조각</a></li> <li class=""><a class="link_sub_item" href="/category/4">firebase</a></li> <li class=""><a class="link_sub_item" href="/category/5">expo</a></li> </ul> </nav></div>
메뉴 너비는 300px로 정하고, CSS로 사이드메뉴(menuwrap 클래스)를 왼쪽 바깥으로 이동시켜 안보이게 합니다.
애니메이션 효과는 메뉴 펼침과 닫힘시 이동하는 애니메이션이 보이도록 트랜지션(transition: left .3s ease-in-out;을 사이드메뉴에 지정합니다.
.mobile-menu { display: block; position: fixed; top: 15px; left: 15px; z-index: 500; width: 45px; height: 45px; padding: 5px; background-color: #f0f0f0; border: 0;}.mobile-menu i { font-size: 2em;}.menuwrap { position: fixed; top: 0; left: -300px; /* 너비 300px 인 사이드바를 왼쪽으로 300px 이동시켜 화면에 보이지 않게 함 */ z-index: 400; overflow: auto; width: 300px; /* 너비 */ height: 100%; padding: 50px 20px; box-sizing: border-box; transition: left .3s ease-in-out; /* 0.3초 동안 이동 애니메이션 */ background-color: #f0f0f0;}
사이드 메뉴를 호출하는 아이콘은 폰트어썸(FontAwesome) 오픈소스 아이콘을 사용해 햄버거 아이콘으로 표시합니다.
폰트어썸 CSS 링크는 아래와 같습니다. HTML 헤더 영역에 추가합니다.
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css" crossorigin="anonymous">
메뉴 펼침/닫기 구현
먼저 사이드메뉴 호출 아이콘에 클릭 이벤트 리스너를 등록합니다.
메뉴 펼침/닫힘은 "on" 클래스를 추가하거나 제거해서 이동 애니메이션을 부여하는 방식으로 구현합니다.
document.addEventListener('DOMContentLoaded', function(){ document.querySelector(".mobile-menu").addEventListener("click", function(e){ if ( document.querySelector('.menuwrap').classList.contains('on') ){ //메뉴닫힘 document.querySelector('.menuwrap').classList.remove('on'); } else { //메뉴펼처짐 document.querySelector('.menuwrap').classList.add('on'); } });});
"on" 클래스는 다음과 같습니다. 위치만 0으로 지정해 애니메이션 동작만 발생시킵니다.
.menuwrap.on { left: 0;}
모바일 메뉴 호출 아이콘을 클릭하면 사이드 메뉴가 왼쪽에서부터 이동해서 나타납니다.
아이콘을 다시 누르면 메뉴가 왼쪽으로 이동해서 사라집니다.
메뉴가 펼쳐졌을 때 아이콘이 닫기(X) 모양이 되도록 아이콘 변경 처리를 CSS에 추가합니다.
document.addEventListener('DOMContentLoaded', function(){ document.querySelector(".mobile-menu").addEventListener("click", function(e){ if ( document.querySelector('.menuwrap').classList.contains('on') ){ //메뉴닫힘 document.querySelector('.menuwrap').classList.remove('on'); document.querySelector('.mobile-menu i').classList.remove('fa-times') document.querySelector('.mobile-menu i').classList.add('fa-bars'); } else { //메뉴펼침 document.querySelector('.menuwrap').classList.add('on'); document.querySelector('.mobile-menu i').classList.remove('fa-bars'); document.querySelector('.mobile-menu i').classList.add('fa-times'); } });});
CSS 없이 자바스크립트로 메뉴 슬라이드 애니메이션을 구현하려면 애니메이션 함수를 추가로 만들어서 클릭 이벤트 리스너에 추가해야 됩니다.
여기서는 CSS를 사용해 간편하게 구현합니다.
스크롤 락 구현
메뉴 슬라이드는 잘 동작하지만, 메뉴가 모바일 화면 전체를 가리는게 아니기 때문에 웹페이지 일부가 메뉴 우측 영역에 보이게 됩니다.
사용자가 의도하지 않았지만 이 영역은 터치하거나 스크롤 할 수 있기 때문에 웹페이지의 하이퍼링크 클릭이나 스크롤이 발생할 수 있습니다.
즉, 모바일 메뉴는 표시되고 있지만, 웹페이지 영역에도 접근 가능한 불편한 상황이 연출됩니다.
메뉴가 펼쳐졌으면, 메뉴 항목만 터치를 할 수 있도록 해야 사용자가 UI를 사용하는데 혼동을 주지 않습니다.
이런 의도치 않은 사용자 액션을 방지하기 위해 메뉴가 펼쳐지면 메뉴 배경에 레이어(Layer))를 깔아서 사용자 터치가 웹페이지에 전달되지 않도록 막아야 합니다.
물론 웹페이지 스크롤도 차단해야 합니다.
"dimmed" 아이디(ID)로 레이어를 하나 만들어서 모바일 메뉴 배경에 배치를 해보겠습니다.
클래스는 다음과 같습니다.
모바일 화면을 꽉채운 영역에 고정 위치로 배치됩니다.
레이어 배경색을 반투명 검정으로 하면 배경에 웹페이지가 약간 비쳐 보이기 때문에, 사용자가 웹페이지에서 벗어났다는 착각을 방지합니다.
#dimmed { position: fixed; top: 0; left: 0; z-index: 300; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5);}
메뉴 펼침/닫힘 동작 조건절에 스크롤락 레이어를 추가하고 삭제하는 코드를 추가합니다.
아울러 스크롤 락 레이어에 모바일 이벤트가 발생할 경우( 스크롤 락 영역에서 스와이프, 터치 ) 이벤트를 차단해서 스크롤 락 밑에 있는 페이지로 이벤트가 넘어가지 않도록 차단해야 합니다.
document.addEventListener('DOMContentLoaded', function(){ document.querySelector(".mobile-menu").addEventListener("click", function(e){ if ( document.querySelector('.menuwrap').classList.contains('on') ){ //메뉴닫힘 document.querySelector('.menuwrap').classList.remove('on'); document.querySelector('.mobile-menu i').classList.remove('fa-times') document.querySelector('.mobile-menu i').classList.add('fa-bars');
//페이지 스크롤 락 해제 document.querySelector('#dimmed').remove(); } else { //메뉴펼침 document.querySelector('.menuwrap').classList.add('on'); document.querySelector('.mobile-menu i').classList.remove('fa-bars'); document.querySelector('.mobile-menu i').classList.add('fa-times');
//페이지 스크롤 락 레이어 추가 let div = document.createElement('div'); div.id = 'dimmed'; document.body.append(div);
//페이지 스크롤 락 모바일 이벤트 차단 document.querySelector('#dimmed').addEventListener('scroll touchmove touchend mousewheel', function(e){ e.preventDefault(); e.stopPropagation(); return false; }); } });});
구현 방법의 차이지만, 메뉴가 슬라이드 될 때 메뉴가 화면 전체를 차지하도록 하면 스크롤 락을 추가하는 과정을 생략할 수 있습니다.
다만, 이렇게 하려면 화면 로딩 시점에 모바일 기기의 너비를 판단해서 모바일 메뉴의 너비를 조정해야 하고, 너비만큼 왼쪽으로 이동된 위치를 정해줘야 하는 추가 과정이 필요합니다.
메뉴 스크롤 락 클릭하면 메뉴 닫히게 하기
완성된 메뉴는 잘 동작하지만, 스크롤락을 막은 화면을 클릭 터치하면 메뉴가 자동으로 닫히게 하는 편의를 부여하면 더 완성도가 높아집니다.
페이지 스크롤락 이벤트 차단 이벤트 리스너 밑에 다음처럼 새 이벤트 리스너를 하나더 추가합니다.
//페이지 스크롤 락 레이어 클릭 메뉴 닫기 document.querySelector('#dimmed').addEventListener('click', function(e){ document.querySelector(".mobile-menu").click(); });
클릭 이벤트 발생시 일일이 메뉴 구성 요소들을 처리할 필요 없이 모바일 메뉴를 클릭한 이벤트가 발생(document.querySelector(".mobile-menu").click();)하도록 해서 자동으로 메뉴가 닫히도록 간접 처리를 해줍니다.