NARADESIGN

웹표준, 웹접근성, 유니버설디자인, HTML, CSS, UI, UX, UD


CSS Vertical Navigation Bar.

본문 건너 뛰기

얼마 전 공유했던 ‘CSS Navigation Bar‘는 ‘수평+서브메뉴 드롭다운’ 네비게이션 이었는데요. 오늘 공유하는 것은 ‘수직+서브메뉴 드롭다운’ 네비게이션 입니다. 수평 메뉴보다 수직 메뉴가 모든 면(HTML/CSS/JS)에서 코드가 더 간결하고 만들기도 쉽네요.

특징

  • 중첩 목록(ul>li>ul>li) 구조로 마크업 했습니다.
  • 키보드만으로도 조작이 가능하고 키보드의 접근 순서는 논리적으로 처리되어 있습니다.
  • 서브메뉴 토글에 jQuery의 .slideDown() 및 .slideUp() 효과를 사용 했습니다.
  • 이미지를 한 번 사용 했습니다.

유의사항

간혹 이런 수직 메뉴의 서브 메뉴 펼침 동작을 onmouseover 이벤트 헨들러로 처리하는 경우가 있는데요. 그런 경우 사용성이나 접근성이 정말 나빠집니다. 

사용성이 나빠지는 이유는 아래쪽에 있는 메뉴를 선택하기 위하여 링크 위를 onmouseover 하는 순간 해당 링크의 서브 메뉴가 펼쳐지는데 이는 보통 원치 않는 동작이기 때문입니다. 

접근성이 나빠지는 이유는 마우스와 동등하게 키보드가 접근할 수 있도록 onmouseover 헨들러와 onfocus 헨들러를 병행해서 작성하지 않는 경우 입니다. 이런 경우 키보드가 서브 메뉴를 펼칠 수 없기 때문에 키보드 사용자는 서브 메뉴에 접근할 수 없습니다. onmouseover 헨들러를 사용했다면 반드시 onfocus 헨들러를 병행 작성하여 동등한 기능을 처리할 수 있도록 해야 합니다.

따라서 수직 메뉴를 드롭다운 형태로 열고 닫는 오늘의 예제와 같은 경우 onmouseover 이벤트 헨들러 사용은 절대적으로 피하는 것이 좋고 onmouseover 이벤트 헨들러 사용은 보편적인 다른 모든 경우에도 최소화 하는 것이 좋습니다.

이 밖에 더 많은 예제들이 OUIF | XEUI 페이지에 링크되어 있습니다.

분류: CSS,웹 디자인,웹 접근성,웹 표준,자바스크립트 | 2010년 3월 10일, 22:15 | 정찬명 | 댓글: 51개 |
트랙백URI - http://naradesign.net/wp/2010/03/10/1212/trackback/

51개의 댓글이 있습니다.

  1. 익명 댓글:

    이런 식의 메뉴라면 토글 화살표가 ▼(접혔을 때), ▲(펼쳐졌을 때) 모양이 되어야 맞지 않을까요?

  2. 정찬명 댓글:

    @익명
    네, 보통은 그렇지요. ^^;
    네이버 개발자 센터에 적용된 디자인을 가져온 것인데요.
    이런 약간의 일탈(?)이 사용성을 떨어뜨리지는 않는다고 판단해서 그대로 살렸습니다.
    http://dev.naver.com/opensource/
    혹시 다른 분들도 어색하다거나 혼란이 오는지 궁금하네요.

  3. 김무건 댓글:

    엘카보다는 실명을 사랑하기로 했습니다 -_-;
    펼침메뉴의 우측 화살표는 button 이 낫지 않나요?
    전에 정찬명님의 글을 읽은 기억으로는 a태그가 페이지 이동을 할 때 사용한다고 들었습니다.
    아니 어쩌면 펼쳐지는 것으로도 페이지 이동을 의미할 수 있겠군요 -_-;

  4. 정찬명 댓글:

    @김무건
    우측 화살표 아이콘은 이 메뉴를 구성하는데 반드시 필요한 요소가 아니라 꾸밈요소라고 판단할 수 있을것 같습니다. 왜냐하면 저 아이콘이 없어도 메뉴를 인지하거나 사용하는데 문제가 없기 때문입니다. 따라서 아이콘은 비어있는 span 요소에 배경이미지 처리가 되어 있구요. 화살표 영역을 포함해서 보다 넓은 직사각형의 블럭이 모두 클릭 영역이기 때문에 따로 화살표 영역에 포커스를 맺히도록 하는 것은 불필요한 것 같습니다. 의견 감사합니다. ^^

  5. 이흥섭 댓글:

    onmouseover로 처리할 경우 원치 않는 메뉴펼침을 방지하기 위해 약간의 딜레이를 주는 방법도 있습니다.
    Firefox 사이트의 대메뉴
    Apple – Downloads의 Downloads 차트(왼쪽에 있는 아코디언)

  6. 청설모 댓글:

    on/off는 색상 쪽에서 포인트를 주는 게 더 눈에 띈다고 생각해서인지,
    그렇게 어색하게 보이지 않네요.

    그리고 .. jQuery 만세! -ㅁ-

  7. 황규연 댓글:

    안녕하세요..찬명님.
    종종 들어오곤 하는데 댓글은 오랜만이네요..ㅎ

    질문이 있습니다. 클릭해서 열고 다시 닫았을때 화살표가 복귀가 안되네요..?
    마우스 이벤트같은 경우 요즘 터치같은곳에서는 작동하기 어려우니 좀 자제를 하는게 좋겠네요.

  8. 정찬명 댓글:

    @이흥섭
    덕분에 항상 댓글이 더 유익하구나. ^^ 딜레이 하는 방법도 좋기는 한데 애플 메뉴가 썩 경쾌하게 느껴지지는 않네. 조금 느리게 움직이면 원치 않는 메뉴가 열리고, 또 너무 빨리 움직이면 즉시 반응하지 않는 것 같고 그래서. 그래도 훌륭한 예제임은 틀림 없는것 같아.

  9. 정찬명 댓글:

    @청설모
    jQuery 만세! (2) ㅎㅎㅎ

  10. 정찬명 댓글:

    @황규연
    닫았을 때에는 화살표가 복귀하도록 개선 해야겠네요. ^^ 감사합니다. 참고로 이 메뉴는 마우스 전용 헨들러는 사용하지 않았습니다. ㅎㅎ

  11. 이승헌 댓글:

    와우…~ 좋은데요^^ 예전에.. 이런거 만들다가 삽질 엄청했는데… 고맙습니다…^^
    접근성이나 사용성에 마우스 이벤트에 관한 말씀도… 새겨들어야 할것 같습니다.

    다시 한번 말씀 드리지만 찬명님때문에…요새 한결…작업이 수월해 졌어요^^
    이제 jquery만 좀 공부하면…^^되는데…

  12. 정찬명 댓글:

    @이승헌
    도움이 되셨다니 정말 기쁩니다. ^^ jQuery 자체는 그리 어렵지 않은 것 같은데 효과적으로 사용하는것은 쉽지 않은것 같네요. 저는 이제 ‘아 이런 것도 되는구나’ 하면서 마구 써보는 중이라서 아직 최척화 개념이 약하네요. 자꾸 써보면서 피드백도 받다보면 뭐 늘겠죠. ㅎㅎ

  13. 마누 댓글:

    slideUp, slideDown 이펙트를 사용하셨네요 ㅎ
    아주 간편한 slideToggle 도 있답니다. 흐흐..

  14. 정찬명 댓글:

    @마누
    감사합니다. slideToggle 효과도 다음에 쓸 기회가 있겠죠. 이번 예제는 if, else if, else 이렇게 조건문이 붙는 바람에 단순하게 토글되지 않아서 사용을 못했답니다. ^^ 좋은 저녁 되세요.

  15. […] Vertical CSS Navigation Bar. […]

  16. 이현희 댓글:

    아주 유용하게 잘 사용하고 있어요~

    저는 이거 처음부터 스크립트 파일에서 display=all 로 모든 메뉴 펼쳐놓고 사용하거든요.. 근데;; 메뉴를 하나를 접으려고 클릭하면, 모든 메뉴가 접히네요..ㅠㅠ

    마우스 클릭한 해당 메뉴만 접히고, 펼쳐지고 할수는 없는지요?
    활기찬 한주 되세요~ ^^

  17. 정찬명 댓글:

    @이현희
    아래 라인을 다음과 같이 주석으로 지우고 테스트 해보세요.
    //sItem.find(‘ul’).attr(‘style’,’display:none’);

  18. 이현희 댓글:

    @정찬명
    답변 글 잘 보았습니다.
    근데;; 알려주신 방법은 초기에 메뉴를 다 접은 상태로 보여주는 것 아닌가요?
    처음 코드가 그렇게 되어 있어서 display:all로 수정해서 리스트 다 펼쳐져 보이게
    한거거든요..

    문제는 펼쳐진 메뉴중 한 메뉴를 접으면 해당 메뉴만 접혀야 하는데 모든 메뉴가 접히는 문제가 발생해서 문의드린거에요~ :)

    도움 부탁드립니다;;

  19. 나그네 댓글:

    메뉴 링크를 넣어서 해당 링크를 클릭했을 때, 한번 클릭으로 페이지 이동이 되지 않고 두번 클릭으로 페이지 이동이 되는건 왜 그럴까요? 첫번째 클릭을 했을 때, li current 클래스 부터 찾고 두번째 클릭을 했을 때, 페이지 이동 처리가 됩니다. 링크가 선언 되었을 때, a 링크가 존재하면 페이지 이동이 먼저 되게 할려면 어떻게 처리 해야 되는지 모르겠네요.

  20. 정찬명 댓글:

    @이현희
    다음 코드를 적용해 보세요.

    
    jQuery(function($){
    	
    	// Side Menu
    	var sMenu = $('div.sMenu');
    	var sItem = sMenu.find('>ul>li');
    	var ssItem = sMenu.find('>ul>li>ul>li');
    	var lastEvent = null;
    	
    //	sItem.find('>ul').css('display','none');
    	sMenu.find('>ul>li>ul>li[class=active]').parents('li').attr('class','active');
    	sMenu.find('>ul>li[class=active]').find('>ul').css('display','block');
    
    	function sMenuToggle(event){
    		var t = $(this);
    		
    		if (this == lastEvent) return false;
    		lastEvent = this;
    		setTimeout(function(){ lastEvent=null }, 200);
    		
    		if (t.next('ul').is(':hidden')) {
    //			sItem.find('>ul').slideUp(100);
    			t.next('ul').slideDown(100);
    //		} else if(!t.next('ul').length) {
    //			sItem.find('>ul').slideUp(100);
    		} else {
    			t.next('ul').slideUp(100);
    		}
    		
    //		if (t.parent('li').hasClass('active')){
    //			t.parent('li').removeClass('active');
    //		} else {
    //			sItem.removeClass('active');
    //			t.parent('li').addClass('active');
    //		}
    	}
    	sItem.find('>a').click(sMenuToggle).focus(sMenuToggle);
    	
    	function subMenuActive(){
    		ssItem.removeClass('active');
    		$(this).parent(ssItem).addClass('active');
    	}; 
    	ssItem.find('>a').click(subMenuActive).focus(subMenuActive);
    	
    	//icon
    	sMenu.find('>ul>li>ul').prev('a').append('');
    });
    
  21. 정찬명 댓글:

    @나그네

    첫 번째 계층의 목록에 대한 링크는 둘 중 한 가지 일 밖에 할 수 없습니다.
    하위 메뉴를 여닫거나 또는 링크 이동을 하거나.
    이 두가지 기능을 동시에 수행하는 것은 불가능 하지요.

    다음코드를 주석 처리 하시면 됩니다.
    단 페이지 이동을 먼저 처리해야 하기 때문에 하위 메뉴는 더 이상 토글되지 않을 것입니다.

    //		if (this == lastEvent) return false;
    //		lastEvent = this;
    //		setTimeout(function(){ lastEvent=null }, 200);
    
  22. 익명 댓글:

    안녕하세요
    귀한 소스를 가지고 열심히 끄적거려 보고 있는 초보입니다.

    바로 위의 경우처럼 링크처리와 기존코드를 섞어서 하고 싶은데…

    예를 들어서
    하위 메뉴가 있다면 기존 코드처럼 토글 기능을 수행을 하고
    하위 메뉴가 없다면 바로 위의 경우처럼 바로 링크처리를 하고 싶은데
    그게 가능할까요??

    있는 지식을 총동원해서 몇가지 해봐도 해답이 나오질 않아서
    도움을 구합니다.

  23. 정찬명 댓글:

    @익명
    네, 이거 문제 입니다. ^^ 다른 여러분들을 통해서도 피드백을 받았는데요. 하위메뉴가 없을 때에는 정상적인 링크 이동 기능을 수행해야 하는것을 제가 처리하지 못했습니다. 조만간 개선을 할 예정인데요. 그때까지 자체적으로 문제를 해결해 보시거나 일단 다른 소스를 활용해 주세요. 감사합니다.

  24. 헬렌 댓글:

    안녕하세요

    정식으로 다시 인사드리고 글 남깁니다.
    (요기 바로 위에 글 남겼던 익명입니다. ^^
    제가 게시판 이용방법이 서툴러서 그냥 등록을 눌러버리는 바람에..)

    오늘 관련 소스를 조금 고민해봤는데
    제가 작업한 경우에 대해서..우선 별도의 class명이 존재하고 있습니다.

    하위메뉴가 있는경우와 없는 경우에 각각의 li 태그에
    class명을 달리 준 상태인데..
    해당 class명을 이용하면 되지 싶어서 끄적거려보는데..
    역시나 j쿼리에 쓰이는 코드가 익숙치 않아서 그런지
    이론적인 생각으로는 웬지 될 꺼 같으면서도 계속 안되고 있네요~ ^^

    저도 열심히 공부해서 해결책을 찾아보겠습니다.

    그동안 눈팅하면서 좋은 정보를 많이 얻었는데
    앞으로는 종종 인사 드리겠습니다.

  25. 정찬명 댓글:

    @헬렌
    저도 jQuery 삽질 무지하게 많이 합니다. 이런 예제들도 한 번에 뚝딱 나오는게 아니라 몇날 몇일 이리고치고 저리고치고 한답니다. 그나저나 이 예제 고쳐야 하는데 좀처럼 시간이 안나네요. ㅜㅜ

  26. 박성호 댓글:

    정찬명님 덕분에 정말 새로운 세계를 경험하고 있습니다.
    뭐 당장은 갖다고 붙여넣기 하는 수준이지만 CSS 소스를 아무리 들여다 봐도
    작동원리를 빨리 깨치기가 힘드네요. ^^;; (CSS의 의미를 모른다기 보다 원리를요)

    덕분에 좋은 소스들을 갖다 씀에 있어서 늘 감사한 마음 갖고 있습니다.
    현재 개발중인 쇼핑몰에 많은 도움이 되고 있습니다. ^^

    고맙습니다. 정찬명님 m(_ _)m

  27. 정찬명 댓글:

    @박성호
    원리는 알고보면 허무할 정도로 간단한걸요. 속성의 값을 바꿔보거나 Firefox+Firebug 도구를 사용하시면 이해하시는데 도움이 되실것 같아요. ^^ 좋은 하루 되세요.

  28. jay 댓글:

    롤오버되면 배경색이 바뀌게 배경색을 검은색으로 넣어봤는데요
    .sMenu li a:hover{ display:block; position:relative; padding:8px 10px; text-decoration:none; color:#666; font-weight:bold; background:#000000; border:1px solid #eee; *zoom:1;}
    서브까지 검은색으로 바뀌는데 따로따로 바뀌게하는 방법 없나요??
    li를 따로도 해보고 div로 class 줘서 바꿔보고 했는데도 제대로 작동이 안되네요 ㅠㅠ

  29. 정찬명 댓글:

    @jay
    선택자를 잘 이해하시면 되는데요. 지금 jay님이 선택하신 a 요소는 다음과 같습니다.

    .sMenu li a:hover

    그리고 이 선택자는 다음과도 같습니다.

    .sMenu li li a:hover

    쉽게말해서 상위 메뉴에 있는 a를 선택했지만 하위 메뉴에 있는 모든 a 요소도 함께 선택한 것입니다. 따라서 하위 메뉴는 다시 원래의 값을 지니도록 reset 할 필요가 있습니다.

    .sMenu li li a:hover 요소를 선택한 다음 { background:#fff; } 속성을 부여하면 됩니다.

    그리고 display, position, padding, text-decoration, color, font-weight, border, zoom 과 같은 부수적인 속성들은 이미 .sMenu li a 선택자에 지정되어 있으므로 다시 지정할 필요가 없습니다. 다시 지정하는 경우 치명적이지는 않겠지만 페이지 렌더링 속도를 느려지게 만드는 요인이 됩니다.

  30. jay 댓글:

    와아~ 되네요 감사합니다~

  31. jay 댓글:

    아무리 생각해도 모르겠어서 질문 하나만 더할게요.
    .sMenu li a:hover{ background:#70cb2d; }
    .sMenu li.active a{ color:#000; border:0; background:#70cb2d;}
    이렇게 롤오버했을때 비지를 주고 선택했을때 비지를 줬는데요
    메인메뉴를 펼쳐놓고 아래 메인메뉴를 클릭하면 전에 선택했던 메뉴 비지가 그대로 남아있는거에요. 그러다가 그 위에 롤오버해주면 다시 없어지고요. 파이어폭스랑 오페라 크롬에서는 그런현상이 없는데 익스플로러에서만 저러는데 이유가 뭘까요?

  32. 정찬명 댓글:

    @jay
    그런 현상을 버그라고 부르지요. ㅎㅎ

    이유는 저도 잘 모르겠구요. 보통 이런 문제를 해결하기 위해서 position:relative 또는 zoom:1 속성을 부여해 보는데 이미 이런 속성이 포함되어 있기 때문에 한 가지 시도를 더 해본다면 width 값을 부여해 보는 것입니다.

    지금 a 요소에 width 값이 없는데요. 메뉴가 가변폭에 대응해야 하는것이 아니라면 width 값을 한번 부여해 보시죠. 물론 잘 될지 안될지 결과는 저도 모릅니다. ^^

    그래도 안되면 어쩔 수 없습니다. 속성을 하나씩 빼면서 테스트 해보고 그래도 안되면 속성을 하나씩 추가하면서 테스트 해보는 수 밖에요. 힘든 일이죠. ㅡㅡ

  33. 디오네 댓글:

    왜 저는 ㅠ.ㅠ 대 메뉴 간격들이 벌어질까요? 완전 초보라 죄송합니다.

  34. 정찬명 댓글:

    @디오네
    제가 답변 드리기에 정보가 너무 부족합니다. ^^

  35. 익명 댓글:

    jquery accordian 메뉴의 width만 줄이면 이거랑 똑같긴하네요 쿨럭!!

  36. 심라 댓글:

    @익명,@정찬명
    서브메뉴가 없는 메뉴는 바로 이동하려면 다음과 같이 하면 되지 않을까요?
    sItem.find(‘]a’).click(sMenuToggle).focus(sMenuToggle); 을
    sItem.find(‘]a[href=#]’).click(sMenuToggle).focus(sMenuToggle); 처럼
    즉, href=”#” 인경우만 클릭이벤트를 등록하는거죠.

  37. 정찬명 댓글:

    @심라
    아주 간단하면서도 멋진 해결책 고맙습니다. 코드에 반영 했습니다. 이제 서브메뉴가 없는 경우 직접 URL 이동할 수 있게 되었네요. ^^b

  38. 강희창 댓글:

    정말 멋진 소스에 감탄하고 갑니다.
    질문사항이 있는데 이거 응용해서 3뎁스, 4뎁스도 가능하게 할려면 js를 어떻게 수정해야 하나요? js는 간단한것 밖에 볼 줄 몰라서 어렵기만 합니다.

    메일로 보내주시면 정말 감사할거 같아요. chocobo앳nate.com

  39. 정찬명 댓글:

    @강희창
    3, 4 단계까지 구현해야 한다면 이 예제보다 Tree 구조 예제를 사용하시는 편이 좋을것 같습니다.
    http://naradesign.net/ouif/uio/navigation/vertical/tree/xhtml.html
    물론 CSS/JS를 추가로 수정해서 사용하셔야 하는데 JS를 수정하시려면 jQuery에 대해서 조금만 공부를 해보시면 가능하실껍니다.

  40. 강희창 댓글:

    4단계까지는 아니고 3단계만 추가하면 되는건데, 간단한 것만은 아니었군요. ^^

    jquery를 하기에는 js만 아주 조금 아는 상태라서 아직은 내공이 부족한거 같네요.

    트리구조를 이용해서 해봐야겠습니다.

    좋은 답변과 좋은 예제를 주셔서 너무나 감사합니다.

    많은 도움이 되었습니다.

  41. 정진택 댓글:

    최상위 메뉴 간 사이의 공백을 넣으려면
    .vNav ul li{margin-bottom:5px;}을 넣어주는게 맞겠죠?

    이렇게 넣어보니 메뉴가 닫힐때
    margin영역가지 까지 접혀 버려 딱 달라붙더라구요
    이 공백을 보존 하려면 어떻게 해야될까요

  42. 정진택 댓글:

    아 내용을 한가지 빼먹었네요,,
    ie8에서만 그런 현상이 일어나네요 ㅠㅠ

  43. 정찬명 댓글:

    @정진택
    이 정도 문제는 직접 해결하실 수 있을꺼라 믿습니다. IE 6~7도 함께 확인해 보세요. ^^

  44. 김지만 댓글:

    대매뉴도 롤오버하면 색깔변경하고 싶은데 어떻게 해야할지 잘 이해가 안갑니다.

    어떻게하면 해야할까요?

  45. 김진영 댓글:

    궁금한것들 이곳에서 확인하면서
    유용한 정보 얻어가고 있습니다.
    항상 감사합니다.

    이번에
    세로 메뉴를 작성해야하는데요

    텍스트가 아닌 이미지로할 때엔
    텍스트 대신 이미지주소를 적은 후에
    온오프는 어찌 해야 할지 궁금합니다.

  46. 정찬명 댓글:

    @김진영
    그런 경우는 보통 텍스트로 마크업 한 다음 이미지는 CSS 배경으로 처리하는 IR 기법을 사용합니다. a 요소의 배경으로 CSS 배경이미지를 노출하고 텍스트는 화면에서 보이지 않도록 감추는 방법입니다. 이 때 화면낭독기와 같은 보조기기가 텍스트를 읽을 수 있도록 하려면 display:none 으로 감추면 안됩니다. 글자가 박스를 넘치도록 만든 다음 overflow:hidden 처리하면 글자가 보이지 않게 되는 트릭을 사용할 수 있습니다.

    그 다음 .active 클래스가 포함된 li>a 요소의 배경이미지를 변경하는 것이죠. 이 때 여러장의 이미지는 쪼개는 것보다 하나로 병합한 다음 CSS background-position 값을 변경해 주는 방법으로 처리하면 성능에 훨씬 좋습니다.

  47. 익명 댓글:

    이 소스 사용하려면 어떻게 해야 하나요? 파이어폭스 개발자 메뉴로 css 소스와 html 소스는 얻었는데 … JS 소스는 어떻게 해야할지 ㅠㅠ

  48. 어려운css 댓글:

    궁금한것이 있는데요…마우스 커서모양이 손모양으로 제대로 바뀌질 않는데, 이부분은 해결하기 어려운가요?

  49. 정찬명 댓글:

    @어려운css
    {cursor:pointer} 속성을 적용해 보세요. CSS는 어렵지 않아요. ㅎㅎ

  50. 어려운css 댓글:

    빠르고 명쾌한 답변 정말 감사드립니다.
    보안문제로, 메일로 문의드리고 싶은것이 더 있는데요~
    메일주소가..특이해서요…
    dece24앳gmail.com <–이거 맞나요?ㅠㅠ
    불쌍한 디자이너를 구제해주소서 ㅠ.ㅠ

  51. 정찬명 댓글:

    @어려운css
    ‘앳’을 ‘@’으로 바꿔서 메일을 보내시면 됩니다. 원래 메일로 질문을 받지 않는데요. 오늘은 기분이 좋으니까 받아 드릴게요. ㅎㅎ

댓글 쓰기

전송된 글이 나타나지 않는다면 필터링 된 것입니다. dece24앳gmail.com 으로 메일 주세요.
(X)HTML 코드 사용이 가능하지만 소스 코드 출력을 원하시면 <꺽쇠>는 [괄호]로 변환하여 작성해 주세요.

필수 아님

필수 아님