스케치북 게시판 스킨 분석¶
인기 있는 스케치북 게시판 스킨의 구조와 특징을 분석하고 커스터마이징 방법을 학습합니다.
스케치북 스킨 구조¶
디렉토리 구조¶
modules/board/skins/sketchbook5/
├── skin.xml # 스킨 정보 및 설정
├── board.default.css # 기본 CSS
├── board.mobile.css # 모바일 CSS
├── js/
│ ├── board.js # 메인 JavaScript
│ └── jquery.plugins.js # jQuery 플러그인
├── img/ # 이미지 리소스
├── _header.html # 공통 헤더
├── _footer.html # 공통 푸터
├── list.html # 목록 페이지
├── write_form.html # 글쓰기 폼
├── view_document.html # 글 보기
├── comment_form.html # 댓글 폼
└── _comment.html # 댓글 목록
주요 특징¶
- 반응형 디자인: 모바일/태블릿/PC 완벽 대응
- 다양한 리스트 타입: 웹진형, 갤러리형, FAQ형 등
- 풍부한 설정 옵션: 관리자에서 세밀한 커스터마이징 가능
- 확장 기능: 무한스크롤, 라이트박스, 소셜 공유 등
리스트 타입 분석¶
웹진형 리스트¶
<!-- 웹진형 리스트 구조 -->
<div class="bd_lst_wrp">
<section class="bd_lst bd_zine">
<article loop="$document_list=>$no,$document" class="zine-item">
<!-- 썸네일 영역 -->
<div class="thumb-area">
{@
$thumb_exist = false;
if($document->thumbnailExists()) {
$thumb_exist = true;
$thumbnail = $document->getThumbnail($mi->thumbnail_width, $mi->thumbnail_height, $mi->thumbnail_type);
}
}
<a href="{getUrl('document_srl',$document->document_srl)}" class="thumb">
<img cond="$thumb_exist" src="{$thumbnail}" alt="" />
<span cond="!$thumb_exist" class="no-img">No Image</span>
</a>
<!-- 카테고리 뱃지 -->
<span cond="$document->get('category_srl')" class="cat-badge">
{$category_list[$document->get('category_srl')]->title}
</span>
</div>
<!-- 콘텐츠 영역 -->
<div class="content-area">
<h3 class="title">
<a href="{getUrl('document_srl',$document->document_srl)}">
{$document->getTitle($mi->subject_cut_size)}
<span class="comment-count" cond="$document->getCommentCount()">
[{$document->getCommentCount()}]
</span>
</a>
</h3>
<p class="excerpt">
{$document->getSummary($mi->content_cut_size)}
</p>
<div class="meta">
<span class="author">{$document->getNickName()}</span>
<span class="date">{$document->getRegdate('Y.m.d')}</span>
<span class="read">조회 {$document->get('readed_count')}</span>
</div>
</div>
</article>
</section>
</div>
갤러리형 리스트¶
<!-- 갤러리형 리스트 구조 -->
<section class="bd_lst bd_gallery">
<ul class="gallery-container" data-columns="{$mi->gallery_cols ?: 4}">
<li loop="$document_list=>$no,$document" class="gallery-item">
<div class="item-inner">
<!-- 썸네일 -->
<div class="thumb-wrap">
<a href="{getUrl('document_srl',$document->document_srl)}">
{@$thumbnail = $document->getThumbnail($mi->thumbnail_width, $mi->thumbnail_height)}
<img src="{$thumbnail}" alt="" cond="$thumbnail" />
<span class="no-img" cond="!$thumbnail">No Image</span>
<!-- 호버 오버레이 -->
<div class="overlay">
<span class="view-more">자세히 보기</span>
</div>
</a>
</div>
<!-- 정보 -->
<div class="info-wrap">
<h4 class="title">
<a href="{getUrl('document_srl',$document->document_srl)}">
{$document->getTitle()}
</a>
</h4>
<div class="meta">
<span class="date">{$document->getRegdate('Y.m.d')}</span>
<span class="hit">조회 {$document->get('readed_count')}</span>
</div>
</div>
</div>
</li>
</ul>
</section>
스케치북 고유 기능¶
무한 스크롤¶
// 스케치북의 무한 스크롤 구현
(function($) {
var $container = $('.bd_lst');
var loading = false;
var page = 1;
var total_page = window.total_page || 1;
function loadMore() {
if(loading || page >= total_page) return;
loading = true;
page++;
$.ajax({
url: current_url,
data: {
page: page,
xhr: 1 // AJAX 요청 표시
},
success: function(html) {
var $html = $(html);
var $items = $html.find('.bd_lst > li, .bd_lst > article');
$container.append($items);
// 이미지 레이지 로딩 재초기화
if(window.lazyLoadInstance) {
window.lazyLoadInstance.update();
}
loading = false;
}
});
}
// 스크롤 이벤트
$(window).scroll(function() {
var scrollTop = $(window).scrollTop();
var windowHeight = $(window).height();
var documentHeight = $(document).height();
if(scrollTop + windowHeight > documentHeight - 200) {
loadMore();
}
});
})(jQuery);
뷰어 모드¶
<!-- 스케치북 뷰어 모드 -->
<div class="bd_viewer" cond="$mi->use_viewer == 'Y'">
<div class="viewer-container">
<!-- 이전 글 -->
<a cond="$oDocument->getPrevDocument()"
href="{getUrl('document_srl', $oDocument->prev_document)}"
class="viewer-prev">
<i class="xi-angle-left"></i>
</a>
<!-- 현재 글 내용 -->
<div class="viewer-content">
<h1 class="title">{$oDocument->getTitle()}</h1>
<div class="content">
{$oDocument->getContent(false)}
</div>
<!-- 첨부 이미지 자동 삽입 -->
<div class="attached-images" cond="$mi->auto_attach_image == 'Y'">
{@$uploaded_list = $oDocument->getUploadedFiles()}
<img loop="$uploaded_list=>$key,$file"
cond="$file->direct_download == 'Y' && preg_match('/\.(jpg|jpeg|gif|png)$/i', $file->source_filename)"
src="{$file->download_url}"
alt="" />
</div>
</div>
<!-- 다음 글 -->
<a cond="$oDocument->getNextDocument()"
href="{getUrl('document_srl', $oDocument->next_document)}"
class="viewer-next">
<i class="xi-angle-right"></i>
</a>
</div>
<!-- 키보드 네비게이션 -->
<script>
$(document).keydown(function(e) {
if(e.keyCode == 37 && $('.viewer-prev').length) {
location.href = $('.viewer-prev').attr('href');
} else if(e.keyCode == 39 && $('.viewer-next').length) {
location.href = $('.viewer-next').attr('href');
}
});
</script>
</div>
대댓글 시스템¶
<!-- 스케치북 대댓글 구조 -->
<div class="comment-list">
<article loop="$oDocument->getComments()=>$key,$comment" class="comment-item" id="comment_{$comment->comment_srl}">
<div class="comment-header">
<!-- 프로필 이미지 -->
<div class="profile">
{@$profile_image = $comment->getProfileImage()}
<img src="{$profile_image->src}" alt="" cond="$profile_image" />
<span class="no-profile" cond="!$profile_image">{$comment->getNickName()}</span>
</div>
<div class="meta">
<span class="author">{$comment->getNickName()}</span>
<span class="date">{$comment->getRegdate('Y.m.d H:i')}</span>
</div>
</div>
<div class="comment-body">
<!-- 대댓글인 경우 원댓글 표시 -->
<div cond="$comment->parent_srl" class="reply-info">
{@$parent_comment = $comment->getParentComment()}
<span class="reply-to">@{$parent_comment->getNickName()}</span>
</div>
<div class="content">
{$comment->getContent(false)}
</div>
</div>
<div class="comment-footer">
<button type="button" class="reply-btn" onclick="toggleReplyForm({$comment->comment_srl})">
답글
</button>
<div class="comment-actions" cond="$comment->isGranted()">
<button type="button" onclick="toggleModifyForm({$comment->comment_srl})">수정</button>
<button type="button" onclick="deleteComment({$comment->comment_srl})">삭제</button>
</div>
</div>
<!-- 대댓글 작성 폼 -->
<div class="reply-write-form" id="reply_form_{$comment->comment_srl}" style="display:none;">
<form action="./" method="post" onsubmit="return procFilter(this, insert_comment)">
<input type="hidden" name="document_srl" value="{$oDocument->document_srl}" />
<input type="hidden" name="parent_srl" value="{$comment->comment_srl}" />
<textarea name="content" placeholder="답글을 입력하세요"></textarea>
<div class="btn-area">
<button type="submit">답글 작성</button>
<button type="button" onclick="toggleReplyForm({$comment->comment_srl})">취소</button>
</div>
</form>
</div>
</article>
</div>
스킨 설정 활용¶
skin.xml 분석¶
<!-- 스케치북 skin.xml 주요 설정 -->
<skin version="0.2">
<title xml:lang="ko">스케치북5</title>
<description xml:lang="ko">반응형 게시판 스킨</description>
<version>5.0</version>
<date>2024-01-01</date>
<author>
<name>개발자명</name>
<email>email@example.com</email>
</author>
<extra_vars>
<!-- 리스트 타입 -->
<var name="list_type" type="select">
<title xml:lang="ko">목록 타입</title>
<options value="webzine">웹진형</options>
<options value="gallery">갤러리형</options>
<options value="list">리스트형</options>
<options value="faq">FAQ형</options>
</var>
<!-- 썸네일 설정 -->
<var name="thumbnail_width" type="text">
<title xml:lang="ko">썸네일 가로 크기</title>
<default>200</default>
</var>
<var name="thumbnail_height" type="text">
<title xml:lang="ko">썸네일 세로 크기</title>
<default>150</default>
</var>
<!-- 기능 설정 -->
<var name="use_infinite_scroll" type="select">
<title xml:lang="ko">무한 스크롤 사용</title>
<options value="Y">사용</options>
<options value="N">사용 안함</options>
</var>
<var name="use_viewer" type="select">
<title xml:lang="ko">뷰어 모드 사용</title>
<options value="Y">사용</options>
<options value="N">사용 안함</options>
</var>
</extra_vars>
</skin>
설정값 활용¶
<!-- 스킨 설정값 사용 예제 -->
{@
// 기본값 설정
if(!$mi->list_type) $mi->list_type = 'webzine';
if(!$mi->thumbnail_width) $mi->thumbnail_width = 200;
if(!$mi->thumbnail_height) $mi->thumbnail_height = 150;
if(!$mi->gallery_cols) $mi->gallery_cols = 4;
}
<!-- 리스트 타입에 따른 분기 -->
<include cond="$mi->list_type == 'webzine'" target="_list_webzine.html" />
<include cond="$mi->list_type == 'gallery'" target="_list_gallery.html" />
<include cond="$mi->list_type == 'list'" target="_list_normal.html" />
<include cond="$mi->list_type == 'faq'" target="_list_faq.html" />
<!-- 반응형 갤러리 컬럼 수 -->
<style cond="$mi->list_type == 'gallery'">
@media (min-width: 768px) {
.gallery-item { width: {100 / $mi->gallery_cols}%; }
}
@media (max-width: 767px) {
.gallery-item { width: 50%; }
}
@media (max-width: 480px) {
.gallery-item { width: 100%; }
}
</style>
커스터마이징 예제¶
새로운 리스트 타입 추가¶
<!-- 카드형 리스트 추가 -->
<!-- _list_card.html -->
<section class="bd_lst bd_card">
<div class="card-container">
<div loop="$document_list=>$no,$document" class="card-item">
<div class="card">
<!-- 카드 헤더 -->
<div class="card-header">
{@$category = $category_list[$document->get('category_srl')]}
<span class="category" style="background-color: {$category->color}">
{$category->title}
</span>
<span class="date">{$document->getRegdate('Y.m.d')}</span>
</div>
<!-- 카드 바디 -->
<div class="card-body">
<h3 class="title">
<a href="{getUrl('document_srl',$document->document_srl)}">
{$document->getTitle()}
</a>
</h3>
<p class="excerpt">
{$document->getSummary(200)}
</p>
<!-- 태그 -->
<div class="tags" cond="$document->get('tag_list')">
<span loop="$document->get('tag_list')=>$tag" class="tag">
#{$tag}
</span>
</div>
</div>
<!-- 카드 푸터 -->
<div class="card-footer">
<div class="author">
{@$profile = $document->getProfileImage()}
<img src="{$profile->src}" alt="" cond="$profile" />
<span>{$document->getNickName()}</span>
</div>
<div class="stats">
<span><i class="xi-eye"></i> {$document->get('readed_count')}</span>
<span><i class="xi-comment"></i> {$document->getCommentCount()}</span>
<span><i class="xi-heart"></i> {$document->get('voted_count')}</span>
</div>
</div>
</div>
</div>
</div>
</section>
<style>
.bd_card .card-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
}
.card {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
overflow: hidden;
transition: transform 0.3s;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 4px 16px rgba(0,0,0,0.15);
}
</style>
모범 사례¶
- 설정 활용: skin.xml의 설정을 최대한 활용
- 구조 유지: 스케치북의 기본 구조 유지하며 확장
- 반응형 고려: 모든 커스터마이징에 반응형 적용
- 성능 최적화: 무거운 기능은 옵션으로 제공
- 호환성: 라이믹스 버전별 호환성 체크
다음 단계¶
스케치북 스킨 분석을 완료했다면, 레이아웃 개발로 진행하세요.