Lavalamp Style Navigation



Script 작성하기

변수 선언 및 문서 객체 참조

먼저 문서 내부의 대상을 참조(reference)해 보겠습니다. 참조할 대상은 #navigation, #navigation 내부의 .focus, 내비게시션 위에 움직일 라바(lava) 입니다. 참조하는 이유는 참조 대상을 일단 메모리에 저장해 놓으면 다시 찾는 수고를 하지 않아도 되기 때문입니다. 흔히 메모리에 미리 저장해 두는 것을 '캐시(cache)'라고 부르는데, 캐시를 사용하면 그 만큼 성능(performance)을 향상시킬 수 있습니다.

						(function ($) { // self_exe function start
							$(function(){ // jQuery Ready() start

								// 변수 선언및 문서 객체 참조
								var $nav = $("#navigation"), // $nav변수에 #navigation을 찾은 jQuery 객체를 참조.
								$current_item = $nav.find('.focus'). // $current_item 변수에 $nav 내부에서 찾은 .focus 객체를 참조.
								$lava = $('<li class="lava'' />');	// $lava 변수에 동적으로 생성한 <li class="lava'' />를 참조.
								
							}); //end Ready
						})(window.jQuery)								
						

jQuery의 .appendTo() 메서드를 이용하여 $nav 참조 객체에서 <ul>을 찾고, <ul>코드의 맨 뒤에$lava를 삽입합니다.

							$lava.appendTo($nav.find('ul'));					
						


라바 스타일링

이번에는 $lava를 스타일링해보겠습니다. 대개 스타일링은 CSS로 하지만 상황에 따라 Javascript에서도 할 수 있습니다. 이 중에서 jQuery.css() 메서드를 사용하면 손쉽게 스타일을 추가할 수 있습니다. $lava의 가로 폭, 세로 폭, 배경색을 설정해 보겠습니다.

							$lava.css({
								width:$current_item.outerWidth(),
								height:$current_item.outerHeight(),
								backgroundColor: '#eee'
							}).appendTo($nav.find('ul'));		
						


라바 위치 지정

이번에는 위치를 설정해 보겠습니다. $lava를 정대 위치로 지정하고, 기점인 $nav를 상대 위치로 지정합니다. top, left 값을 현재 활성화된 $current_item의 위치 값으로 설정해서 현재 활성화된 위치로 옮깁니다. 먼저 $nav 참조 객체에 .css() 메서드를 사용해서 상대 위치를 지정하겠습니다.

이어서 $lava 참조 객체의 .css() 메서드 안에 절대 위치로 지정하고 top과 left 값을 설정하는 코드를 추가합니다.

파이어버그로 확인하면 설정된 값이 제대로 동작하여 $lava 위치 값이 $current_item 위치 값과 같아진 것을 알 수 있습니다. 그런데 내비게이션 아이템의 앞부분인 [Home]이 $lava에 가려서 보이지 않습니다. [Home]이 왜 $lava 아래로 숨어버린 걸까요? 그 이유는 $lava 객체의 z축 높이가 더 높기 때문입니다. 이 문제도 쉽게 해결 할 수 있습니다. 즉. $lava보다 $nav 내부 <a> 요소의 z축 높이를 높이면 됩니다. 코드를 작성합니다.

							$nav.css('position', 'relative')
								.find('a').css({
									position:'relative',
									zIndex:1
								});
							$lava.css({
								position:'absolute',
								top: $current_item.position().top,
								left: $current_item.position().left,

								width:$current_item.outerWidth(),
								height:$current_item.outerHeight(),
								backgroundColor: '#eee'
							}).appendTo($nav.find('ul'));		
						


라바 그레이디언트 적용하기

이번에는 $lava에 CSS 파트에서 만든 그레이디언트 디자인을 적용시켜 보겠습니다. 이 방법도 간단합니다. lavalamp.css 파일에 설정한 그레이디언트 디자인에 .lava 선택자만 추가하면 됩니다.



라바 위치 조절하기

그런데 $lava 높이 값이 CSS에서 디자인한 것과 약간 달라보입니다. 그 이유는 $current_item 변수가 참조하는 객체가 li.focus이기 때문입니다. CSS에서 디자인했을 때는 li.focus가 아닌 li.focus a 입니다. 브라우저를 실행시켜 보면 현재 설정된 $lava 높이보다 높다는 것을 알 수 있습니다.
코드를 수정해서 높이 값을 디자인에 맞춰보겠습니다. $lava 높이는 $current_item의 outerHeight() 높이 값이 설정 되어 있습니다. 여기에 20px을 더해보겠습니다. 코드를 저장한 수에 확인해보면 %lava 높이 폭이 20만큼 커진 것을 알 수 있습니다.

							height:$current_item.outerHeight()+20,

							top: $current_item.position().top - (20/2),
							
						

하지만 이후의 코드 수정까지 고려하면 상수인 20을 변수로 수정하는 것이 좋습니다. 상수로 두면 이후에 수정할 때 두 번 고쳐야 하고, 단순힌 값만 같은 것인지, 같은 의미인지 알지 못해서 빠뜨릴 수도 있기 때문입니다. 변수 gap에 20을 대입하고, 상수 20을 변수로 바꿉니다.

							// 옵션
							gap = 20; // $lava의 높이 폭 설정.

							height:$current_item.outerHeight() + gap,

							top: $current_item.position().top - gap/2,
							
						


이벤트 감지와 구현하기

$nav의 li에 마우스 포인터를 올려놓거나 포커스 상태가 되었을 때를 알아채고, 알아챘을 때 어떻게 처리해야 할 것인지 코드를 작성해 보겠습니다. 먼저 마우스 포인터를 올려 놓거나 키보드의 탭 키가 포커스되어 올라갔을 때와 그렇지 않을 때를 알아채는 코드를 작성해 보겠습니다.

								// $nav li에 마우스/포커스 이벤트 핸들링
								$nav.find('li')
								.bind('mouseover focusin', function () {
										// 마우스 오버, 포커스 인 상태에서 수행할 코드
								})
								.bind('mouseout focusout', function () {
									// 마우스 아웃, 포커스 아웃 상태에서 수행할 코드
								});
						

우선 마우스 포인터를 올려놓거나 키보드의 탭 키가 포터스되어 올라간 상태가 되면 $lava 의 위치를 마우스 포인터를 올려놓은 li 위치로 옮겨야 합니다. jQuery의 .animate() 메서드를 써서 부드럽게 옮길 것입니다. 이때 바꿔줄 속성은 이동할 위치(left)와 이동한 li의 가로 폭(width)입니다. 그래야만 라바 램프의 디자인 콘셉트에 맞는 모양이 나옵니다. 이제 코드를 작성해 보겠습니다.

								// $nav li에 마우스/포커스 이벤트 핸들링
								$nav.find('li')
								.bind('mouseover focusin', function () {
									$lava.animate({
										left: $(this).position().left,
										width:$(this).outerWidth()
									}, {
										duration: 400,
										easing: 'easeInOutElastic',
										queue: false
									});
								})
								.bind('mouseout focusout', function () {
									// 마우스 아웃, 포커스 아웃 상태에서 수행할 코드
								});
						

코드를 보면 앞에서 사용한 .animate() 작성 방식과 달라보일 것입니다. 중괄호({}) 2개 중 뒤에 있는 중괄호를 살펴 보겠습니다. duration은 '지속시간'을 의미하고, 단위는 밀리초(1/1000초)를 사용합니다. easing은 '이징 곡선'을 의미하고, jQuery Easing 플러그인(http://gsgd.co.uk/sandbox/jquery/easing)을 키워드로 사용한 것입니다. 여기서 사용한 키워드는 '탄력적으로 움직임'입니다.

마지막으로 queue는 애니메이션 적용 순서입니다. 일반적으로 애니메이션이 순차적으로 적용되지만, false 값을 쓰면 여러 애니메이션을 동시에 수행합니다. 여기서는 사용자가 li에 여러 번 마우스 포인터를 올렸다 내렸다 해도 한 번만 수행할 수 있도록 설정하는 역할을 합니다. 이미 사용해 봤던 .stop()과 비슷해 보이지만 .stop()은 기존 애니메이션을 멈추고 새 애니메이션을 수행하는 구조인데 반해, Queue:false 옵션은 기존 애니메이션이 끝나기를 기다리지 않고 새 애니메이션을 동시에 수행시키는 것이 다릅니다. 코드를 저장하고 웹 브라우저에서 확인해 보면 탄력적으로 움직이는 $lava를 볼 수 있습니다.

이제 마우스 포인터를 내려놓고 키보드의 탭 키의 포커스 아웃된 상태에서 처리할 동작을 만들어 보겠습니다. 잠시 멈추었다가 $current_item(현재 활성화된 아이템)으로 다시 돌아가면 됩니다. 이때 쓰기 좋은 함수가 setTimeout() 입니다. setTimeout()은 일정 시간이 지나면 동작을 한 번 수행하는 함수입니다. 시간은 '멈출 시간'이고, 시간 단위는 '밀리초'입니다.

								// $nav li에 마우스/포커스 이벤트 핸들링
								$nav.find('li')
								.bind('mouseover focusin', function () {
									// 마우스 오버, 포커스 인 상태에서 수행할 코드
								})
								.bind('mouseout focusout', function () {
									// 마우스 아웃, 포커스 아웃 상태에서 수행할 코드
									setTimeout(function () {
										$lava.animate({
											left: $current_item.position().left,
											width:$current_item.outerWidth()
										}, 200);	
									}, 2000);
								});
						

네임스페이스 만들기

이로써 라바 램프 스타일 내비게이션 스크립트 코드가 마무리 되었습니다. 그런데 지금까지 만든 코드는 유지 보수가 쉽지 않습니다. 유지 보수까지 고려하면 Javascript 네임스페이스 객체를 만들어 관리하는 것이 좋습니다. options 네임스페이스 객체를 하나 만들어 수정이 잦은 값들을 담아 설정합니다. 코드는 아래와 같습니다.

							//옵션
							options = {
								gap: 20,
								speed: 400,
								easing: 'easeInOutElastic',
								reset: 2000
							};
							
							// namespace 적용 대상 영역
							top: $current_item.position().top - options.gap/2,
							height:$current_item.outerHeight() + options.gap,
							duration: options.speed,
							easing: options.easing,
							// 생략
									},  options.speed);	
							}, options.reset);
						

네임스페이스 객체인 options의 각 속성 값에 접근할 수 있도록 코드를 수정해야만 정상적으로 작동합니다. 상기의 적용대상 영역을 수정하고 완성합니다.



플러그인 만들기

이대로 마무리해도 되지만 지금까지 작성한 코드를 jQuery 플러그인으로 만들어 두면 나중에 더 편해집니다. 지금까지 작성한 코드를 lavalamp 플러그인으로 제작하거나 재사용하는 방법을 살펴보겠습니다.


먼저 jQuery 플러그인을 만들어 보겠습니다. js 폴더 안에 jquery.lavalamp.js 파일을 새로 만듭니다. jQuery 플러그인 기본형 코드를 작성합니다. JavaScript는 프로토타입 기반의 상속을 지원하는 언어입니다. jQuery 역시 JavaScript 라이브러리이기 때문에 상속을 지원합니다. jQuery.fn은 jQuery 객체의 prototype의 약식 표현 식이자 jQuery.fn.플러그인 형식으로, jQuery를 플러그인 확장할 수 있습니다.

이번에는 jQuery 플러그인을 만드는 기본 코드에 대해 알아보겠습니다. 기본 코드 내부에 앞서 작성한 코드를 추가하면 플러그인이 완성됩니다.

							$.fn.lavalamp = function () { // jQuery.fn 객체에 lavalamp 이름의 함수를 추가하여 작성합니다. 
								return this.each(function () { // jQuery  체인(연속해서 코드를 처리) 처리할 수 있도록 this를 return합니다. 
									// 플러그인 코드				// jQuery each() 함수는 선택된 대상마다 반복적으로 코드를 처리하는 역할을 수행합니다.
								});
							};
						

플러그인 코드의 나머지는 '대상 참조' 부분만 수정하고 나머지 코드는 그대로 복사해서 갖다 붙이면 됩니다. 처음부터 플러그인 작성을 염두에 두고 코드를 작성했기 때문에 수정할 내용이 줄어든 것입니다.
완성된 플러그인 코드는 아래와 같습니다.

						// lavalamp Plugin
						(function ($) { // self_exe function start
							$.fn.lavalamp = function (options) { 
								
								// 옵션 설정
								options = $.extend({
									gap: 20,
									bgColor:'#eee',
									speed: 400,
									easing: 'easeInOutElastic',
									reset: 2000								
								}, options); 
								
								return this.each(function () {
									
									// 처리할 코드
									console.log(options);

									// 대상 참조
									var $nav = $("#navigation"), 
										$current_item = $nav.find('.focus'), 
										$lava = $('<li class="lava'' />'),
										reset;

									// $lava의 기준요소 $nav 설정 및 a 태그 z-index 높이 조정
									$nav.css('position', 'relative')
										.find('a').css({
											position:'relative',
											zIndex:1
										});

									// $lava 조작 및 스타일링
									$lava.css({
										position:'absolute',
										top: $current_item.position().top - options.gap/2,
										left: $current_item.position().left,

										width:$current_item.outerWidth(),
										height:$current_item.outerHeight() + options.gap,
										backgroundColor: '#eee'
									}).appendTo($nav.find('ul'));

									// $nav li에 마우스/포커스 이벤트 핸들링
									$nav.find('li')
									.bind('mouseover focusin', function () {
										$lava.animate({
											left: $(this).position().left,
											width:$(this).outerWidth()
										}, {
											duration: options.speed,
											easing: options.easing,
											queue: false
										});
									})
									.bind('mouseout focusout', function () {
										// 마우스 아웃, 포커스 아웃 상태에서 수행할 코드
										setTimeout(function () {
											$lava.animate({
												left: $current_item.position().left,
												width:$current_item.outerWidth()
											},  options.speed);	
										}, options.reset);
									})
									// <li> 클릭 시, .focus 추가
									.click(function() {
										$(this)
											.siblings().removeClass('focus')
										.end().addClass('focus');
										$current_item = $(this);
									});
								});// end this.each()
							}; // end $.fn.function
						})(window.jQuery)								
						


※ 참고 ※ lavalamp - Cross Browsing Test MSIE

참고 사이트