최근 게시판을 만들어볼려고 작업을 하고 있었다.
게시판은 정말로 간단한 작업이다. 그렇기 때문에 특별한 기술은 필요없다.
그럼에도 게시판에서도 나름의 UX를 고민해보고 싶어서'페이지네이션(pagination)'에 고민을 해 보았다.
하여, AI의 도움을 받아 TOP3에 해당하는 페이지네이션 UI를 보고 장단점을 고민해 보자.
페이지네이션 목록
1. 네비게이션 타입
2. 컨텍스트 타입
3. 하이브리드 점프 타입
1. 네비게이션 타입
가장 많이 쓰이는 타입 중 하나라고 생각한다.
이 페이지네이션의 핵심은 처음과 맨 마지막 중간즘에 있을때,
가운데 3개를 제외하고 양 옆에 ... 처리하는 방식이다.
장점 | 단점 |
|
|
const getSmartRange = (current, total) => {
const numbers = [];
const addNumber = (num) => numbers.push(num);
const addEllipsis = () => numbers.push('...');
addNumber(1);
if (current <= 4) {
[2, 3, 4].forEach(addNumber);
} else {
addEllipsis();
addNumber(current - 1);
addNumber(current);
}
if (current > 4 && current < total - 3) {
addNumber(current + 1);
addEllipsis();
} else if (current <= 4) {
addEllipsis();
} else {
[total - 2, total - 1].forEach(addNumber);
}
addNumber(total);
return numbers;
};
<section className="space-y-4">
<div className="border rounded-lg p-4">
{generateItems(currentPage).map(item => (
<div key={item.id} className="py-2 border-b last:border-0">
{item.title}
</div>
))}
<nav
className="flex items-center justify-center gap-2 mt-4"
role="navigation"
aria-label="Pagination"
>
<button
onClick={() => setCurrentPage(1)}
disabled={currentPage === 1}
className="p-2 rounded-lg hover:bg-gray-100 disabled:opacity-50"
aria-label="First page"
>
<ChevronsLeft className="w-4 h-4" />
</button>
<button
onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
disabled={currentPage === 1}
className="p-2 rounded-lg hover:bg-gray-100 disabled:opacity-50"
aria-label="Previous page"
>
<ChevronLeft className="w-4 h-4" />
</button>
{getSmartRange(currentPage, totalPages).map((num, idx) => (
<button
key={idx}
onClick={() => typeof num === 'number' && setCurrentPage(num)}
className={`w-10 h-10 rounded-lg ${
currentPage === num
? 'bg-blue-500 text-white'
: typeof num === 'number' ? 'hover:bg-gray-100' : ''
}`}
aria-current={currentPage === num ? 'page' : undefined}
disabled={typeof num !== 'number'}
>
{num}
</button>
))}
<button
onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
disabled={currentPage === totalPages}
className="p-2 rounded-lg hover:bg-gray-100 disabled:opacity-50"
aria-label="Next page"
>
<ChevronRight className="w-4 h-4" />
</button>
<button
onClick={() => setCurrentPage(totalPages)}
disabled={currentPage === totalPages}
className="p-2 rounded-lg hover:bg-gray-100 disabled:opacity-50"
aria-label="Last page"
>
<ChevronsRight className="w-4 h-4" />
</button>
</nav>
<div className="text-center text-sm text-gray-500 mt-2">
{`${(currentPage - 1) * itemsPerPage + 1} - ${Math.min(currentPage * itemsPerPage, totalPages * itemsPerPage)} of ${totalPages * itemsPerPage} items`}
</div>
</div>
</section>
2. 컨텍스트 인식 페이지네이션
현재 내가 어디에 있는지 알 수 있는 컨텍스트 페이지네이션이다.
가장 기본이라고 할 수 있다.
장점 | 단점 |
|
|
<section className="space-y-4">
<div className="border rounded-lg p-4">
{generateItems(currentPage).map(item => (
<div key={item.id} className="py-2 border-b last:border-0">
{item.title}
</div>
))}
<div className="flex items-center justify-between mt-4 text-sm">
<button
onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
className="flex items-center gap-2 px-4 py-2 rounded-lg hover:bg-gray-100"
disabled={currentPage === 1}
>
<ChevronLeft className="w-4 h-4" />
{currentPage > 1 && `이전 ${itemsPerPage}개 항목`}
</button>
<span className="text-gray-500">
페이지 {currentPage} / {totalPages}
</span>
<button
onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
className="flex items-center gap-2 px-4 py-2 rounded-lg hover:bg-gray-100"
disabled={currentPage === totalPages}
>
{currentPage < totalPages && `다음 ${itemsPerPage}개 항목`}
<ChevronRight className="w-4 h-4" />
</button>
</div>
</div>
</section>
3. 하이브리드 점프
페이지 이번 영역을 통해서 한번에 페이지로 이동할 수 있는 장점이있다.
만약 내가 어떤 페이지에 어떤 콘텐츠가 있다는 것을 알고 있다면 굳이
좌우 버튼을 클릭해서 이동할 필요가 없어 좋다.
장점 | 단점 |
|
|
<section className="space-y-4">
<div className="border rounded-lg p-4">
{generateItems(currentPage).map(item => (
<div key={item.id} className="py-2 border-b last:border-0">
{item.title}
</div>
))}
<div className="flex items-center justify-between mt-4">
<button
onClick={() => setShowJumpDialog(!showJumpDialog)}
className="px-4 py-2 text-sm text-gray-600 hover:bg-gray-100 rounded-lg flex items-center gap-2"
>
<Search className="w-4 h-4" />
페이지 이동
</button>
{showJumpDialog && (
<div className="absolute mt-2 p-4 bg-white border rounded-lg shadow-lg">
<div className="flex items-center gap-2">
<input
type="number"
min="1"
max={totalPages}
value={jumpToPage}
onChange={(e) => setJumpToPage(e.target.value)}
className="w-20 px-2 py-1 border rounded"
placeholder="페이지"
/>
<button
onClick={() => {
const page = parseInt(jumpToPage);
if (page >= 1 && page <= totalPages) {
setCurrentPage(page);
setShowJumpDialog(false);
setJumpToPage('');
}
}}
className="px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600"
>
이동
</button>
</div>
</div>
)}
<div className="flex items-center gap-2">
{getSmartRange(currentPage, totalPages).map((num, idx) => (
<button
key={idx}
onClick={() => typeof num === 'number' && setCurrentPage(num)}
className={`w-8 h-8 rounded ${
currentPage === num
? 'bg-blue-500 text-white'
: typeof num === 'number' ? 'hover:bg-gray-100' : ''
}`}
>
{num}
</button>
))}
</div>
</div>
</div>
</section>
각각의 페이지네이션에는 장점이 있다. 따라서 장점에 맞춰서 페이지네이션을 선택하면 좋을 거 같다.
고객이 원하는 것이 없다면, 기본을 선택하면 좋다.
다만, 게시판의 특성을 파악할 수 있다면 조금 더 세부적인 선택이 가능하다고 생각하다.
어떤 게시판에서 페이지네이션을 무한 스크롤로 만들기도 한다.
꼭 위의 내용이 전부는 아니다. 그럼에도 무조건 만들기보다는 고민을 해보는 게 좋은 거 같다.
내가 현재 어떤 페이지를 만들고 있고 어떤 UX가 사용자에게 더 사용성을 높여줄 것인지.
'UI' 카테고리의 다른 글
[ UI ] UI를 그릴 때 고려해야 할 사항 (1) | 2025.02.27 |
---|---|
[ UI / 색상 위계] 메인페이지 시각적 계층구조를 통한 강조 (0) | 2025.01.14 |
[ UI / Pictogram ] '픽토그램'을 왜 사용해야하는가? 글자가 있는데?! (2) | 2024.12.26 |
[UI / 여백의 미 ] 동양미술에서 말하는 '여백의 미'(Feat. white-space ratio) (1) | 2024.12.22 |
[ 인지 / LandingPage ] 어떤 메인 UI가 더 편하게 읽힐까?(3개 홈페이지 비교) (1) | 2024.12.18 |