헤더와 푸터 만들기¶
헤더 구조¶
헤더는 일반적으로 3단 구조로 구성됩니다:
- 상단 (Top Bar): 유틸리티 메뉴, 언어 선택, SNS 링크
- 중단 (Main Header): 로고, 메인 메뉴, 검색
- 하단 (Sub Header): 서브메뉴, 브레드크럼
기본 헤더 구조¶
<header id="header">
<!-- 상단 영역 -->
<div class="header-top">
<div class="container">
<!-- 좌측: 연락처 정보 -->
<div class="contact-info">
<span><i class="icon-phone"></i> 02-1234-5678</span>
<span><i class="icon-email"></i> info@example.com</span>
</div>
<!-- 우측: 유틸리티 메뉴 -->
<div class="utility-menu">
<!--@if(!$is_logged)-->
<a href="{getUrl('act', 'dispMemberLoginForm')}">로그인</a>
<a href="{getUrl('act', 'dispMemberSignUpForm')}">회원가입</a>
<!--@else-->
<a href="{getUrl('act', 'dispMemberInfo')}">마이페이지</a>
<a href="{getUrl('act', 'dispMemberLogout')}">로그아웃</a>
<!--@endif-->
<!-- 언어 선택 -->
<div class="lang-select" cond="count($lang_supported) > 1">
<select onchange="doChangeLangType(this.value)">
<option loop="$lang_supported => $key, $val"
value="{$key}"
selected="selected"|cond="$key == $lang_type">
{$val}
</option>
</select>
</div>
</div>
</div>
</div>
<!-- 메인 헤더 -->
<div class="header-main">
<div class="container">
<!-- 로고 -->
<div class="logo">
<a href="{getUrl()}">
<!--@if($layout_info->logo_image)-->
<img src="{$layout_info->logo_image}" alt="{$layout_info->site_title}" />
<!--@else-->
<h1>{$layout_info->site_title}</h1>
<!--@endif-->
</a>
</div>
<!-- 메인 메뉴 -->
<nav class="main-nav">
<ul class="gnb">
<li loop="$main_menu->list => $key1, $val1"
class="depth1 {$val1['selected'] ? 'active' : ''}">
<a href="{$val1['href']}"
target="_blank"|cond="$val1['open_window'] == 'Y'">
{$val1['link']}
</a>
<!-- 2차 메뉴 -->
<div class="submenu" cond="$val1['list']">
<ul class="depth2">
<li loop="$val1['list'] => $key2, $val2"
class="{$val2['selected'] ? 'active' : ''}">
<a href="{$val2['href']}">{$val2['link']}</a>
<!-- 3차 메뉴 -->
<ul class="depth3" cond="$val2['list']">
<li loop="$val2['list'] => $key3, $val3">
<a href="{$val3['href']}">{$val3['link']}</a>
</li>
</ul>
</li>
</ul>
</div>
</li>
</ul>
</nav>
<!-- 검색 -->
<div class="header-search">
<form action="{getUrl()}" method="get">
<input type="hidden" name="mid" value="{$mid}" />
<input type="hidden" name="act" value="IS" />
<input type="text" name="is_keyword" value="{$is_keyword}"
placeholder="검색어 입력" />
<button type="submit"><i class="icon-search"></i></button>
</form>
</div>
<!-- 모바일 메뉴 버튼 -->
<button class="mobile-menu-toggle">
<span></span>
<span></span>
<span></span>
</button>
</div>
</div>
<!-- 서브 헤더 (선택적) -->
<div class="header-sub" cond="!$is_index">
<div class="container">
<!-- 페이지 타이틀 -->
<h2 class="page-title">{$module_info->browser_title}</h2>
<!-- 브레드크럼 -->
<nav class="breadcrumb">
<a href="{getUrl()}"><i class="icon-home"></i></a>
<block loop="$main_menu->list => $key1, $val1" cond="$val1['selected']">
<span class="separator">/</span>
<a href="{$val1['href']}">{$val1['link']}</a>
<block loop="$val1['list'] => $key2, $val2" cond="$val2['selected']">
<span class="separator">/</span>
<a href="{$val2['href']}">{$val2['link']}</a>
</block>
</block>
</nav>
</div>
</div>
</header>
고정 헤더 (Sticky Header)¶
스크롤 시 헤더를 고정시키는 기능:
// layout.js
jQuery(function($) {
var header = $('#header');
var headerHeight = header.outerHeight();
var scrolled = false;
$(window).scroll(function() {
var scrollTop = $(window).scrollTop();
if (scrollTop > headerHeight && !scrolled) {
header.addClass('fixed');
$('body').css('padding-top', headerHeight);
scrolled = true;
} else if (scrollTop <= headerHeight && scrolled) {
header.removeClass('fixed');
$('body').css('padding-top', 0);
scrolled = false;
}
});
});
/* layout.css */
#header.fixed {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
푸터 구조¶
푸터는 일반적으로 다음 요소들을 포함합니다:
기본 푸터 구조¶
<footer id="footer">
<!-- 푸터 위젯 영역 -->
<div class="footer-widgets">
<div class="container">
<div class="row">
<!-- 회사 정보 -->
<div class="col">
<h3>회사 소개</h3>
<p>{$layout_info->footer_about}</p>
<!-- SNS 링크 -->
<div class="social-links">
<a href="{$layout_info->facebook_url}" cond="$layout_info->facebook_url">
<i class="icon-facebook"></i>
</a>
<a href="{$layout_info->twitter_url}" cond="$layout_info->twitter_url">
<i class="icon-twitter"></i>
</a>
<a href="{$layout_info->instagram_url}" cond="$layout_info->instagram_url">
<i class="icon-instagram"></i>
</a>
</div>
</div>
<!-- 빠른 링크 -->
<div class="col">
<h3>빠른 링크</h3>
<ul class="footer-links">
<li loop="$footer_menu->list => $key, $val">
<a href="{$val['href']}">{$val['link']}</a>
</li>
</ul>
</div>
<!-- 최근 게시물 -->
<div class="col">
<h3>최근 게시물</h3>
{@
// 최근 게시물 가져오기
$args = new stdClass();
$args->module_srl = $layout_info->recent_module_srl;
$args->list_count = 5;
$recent_documents = executeQuery('document.getDocumentList', $args)->data;
}
<ul class="recent-posts">
<li loop="$recent_documents => $doc">
<a href="{getUrl('document_srl', $doc->document_srl)}">
{cut_str($doc->title, 30)}
</a>
<span class="date">{zdate($doc->regdate, 'Y.m.d')}</span>
</li>
</ul>
</div>
<!-- 연락처 정보 -->
<div class="col">
<h3>연락처</h3>
<ul class="contact-list">
<li><i class="icon-location"></i> {$layout_info->address}</li>
<li><i class="icon-phone"></i> {$layout_info->phone}</li>
<li><i class="icon-email"></i> {$layout_info->email}</li>
<li><i class="icon-time"></i> {$layout_info->business_hours}</li>
</ul>
</div>
</div>
</div>
</div>
<!-- 저작권 영역 -->
<div class="footer-bottom">
<div class="container">
<div class="copyright">
<p>
© {date('Y')} {$layout_info->site_title}.
All rights reserved.
<!--@if($layout_info->company_name)-->
| {$layout_info->company_name}
<!--@endif-->
<!--@if($layout_info->business_number)-->
| 사업자등록번호: {$layout_info->business_number}
<!--@endif-->
</p>
</div>
<!-- 푸터 메뉴 -->
<nav class="footer-nav">
<a href="{getUrl('mid', 'privacy')}">개인정보처리방침</a>
<a href="{getUrl('mid', 'terms')}">이용약관</a>
<a href="{getUrl('mid', 'sitemap')}">사이트맵</a>
<!--@if($logged_info->is_admin == 'Y')-->
<a href="{getUrl('', 'module', 'admin')}">관리자</a>
<!--@endif-->
</nav>
</div>
</div>
<!-- 맨 위로 버튼 -->
<button id="back-to-top" title="맨 위로">
<i class="icon-arrow-up"></i>
</button>
</footer>
맨 위로 버튼 스크립트¶
// 맨 위로 버튼
jQuery(function($) {
var $backToTop = $('#back-to-top');
// 스크롤 시 버튼 표시/숨김
$(window).scroll(function() {
if ($(this).scrollTop() > 300) {
$backToTop.fadeIn();
} else {
$backToTop.fadeOut();
}
});
// 클릭 시 맨 위로 스크롤
$backToTop.click(function() {
$('html, body').animate({
scrollTop: 0
}, 600);
return false;
});
});
반응형 헤더/푸터¶
모바일 메뉴¶
<!-- 모바일 메뉴 -->
<div class="mobile-menu-wrapper">
<div class="mobile-menu-header">
<span class="title">메뉴</span>
<button class="close-menu">×</button>
</div>
<nav class="mobile-menu">
<ul>
<li loop="$main_menu->list => $key1, $val1">
<a href="{$val1['href']}" class="has-sub"|cond="$val1['list']">
{$val1['link']}
<span class="arrow" cond="$val1['list']">▼</span>
</a>
<ul class="sub-menu" cond="$val1['list']">
<li loop="$val1['list'] => $key2, $val2">
<a href="{$val2['href']}">{$val2['link']}</a>
</li>
</ul>
</li>
</ul>
</nav>
</div>
반응형 CSS¶
/* 태블릿 */
@media (max-width: 991px) {
.header-top {
display: none;
}
.main-nav {
display: none;
}
.mobile-menu-toggle {
display: block;
}
.footer-widgets .col {
width: 50%;
margin-bottom: 30px;
}
}
/* 모바일 */
@media (max-width: 767px) {
.header-main {
padding: 10px 0;
}
.logo h1 {
font-size: 24px;
}
.footer-widgets .col {
width: 100%;
}
.footer-bottom {
text-align: center;
}
.footer-nav {
margin-top: 10px;
}
}
헤더/푸터 설정 변수¶
info.xml에 추가할 설정 변수들:
<extra_vars>
<!-- 헤더 설정 -->
<var name="logo_image" type="image">
<title xml:lang="ko">로고 이미지</title>
</var>
<var name="sticky_header" type="select">
<title xml:lang="ko">고정 헤더 사용</title>
<options value="Y">
<title xml:lang="ko">사용</title>
</options>
<options value="N">
<title xml:lang="ko">사용안함</title>
</options>
</var>
<!-- 푸터 설정 -->
<var name="footer_about" type="textarea">
<title xml:lang="ko">회사 소개</title>
</var>
<var name="address" type="text">
<title xml:lang="ko">주소</title>
</var>
<var name="phone" type="text">
<title xml:lang="ko">전화번호</title>
</var>
<var name="email" type="text">
<title xml:lang="ko">이메일</title>
</var>
<var name="business_hours" type="text">
<title xml:lang="ko">영업시간</title>
</var>
<!-- SNS 링크 -->
<var name="facebook_url" type="text">
<title xml:lang="ko">Facebook URL</title>
</var>
<var name="twitter_url" type="text">
<title xml:lang="ko">Twitter URL</title>
</var>
<var name="instagram_url" type="text">
<title xml:lang="ko">Instagram URL</title>
</var>
</extra_vars>