728x90
안녕하세요 오늘은 Tailwind CSS 라이브러리를 사용해 페이지네이션을 구현해보도록 하겠습니다.
Tailwind CSS 라이브러리 "유틸리티-퍼스트(utility-first)" CSS 프레임워크" 입니다.
여기서 "라이브러리"라는 표현도 쓰이지만, 정확히는 CSS 프레임워크라고 부르는 것이 더 적절합니다.
- 기존 CSS 프레임워크 (예: Bootstrap)와의 차이점
- Bootstrap: 미리 정의된 컴포넌트(버튼, 카드, 내비게이션 바 등)를 제공하고, 해당 컴포넌트에 btn, card, navbar와 같은 클래스를 부여하여 사용합니다. 디자인이 정해져 있어서 빠르게 만들 수 있지만, 커스터마이징하려면 복잡한 CSS 오버라이딩이 필요할 때가 많습니다.
- Tailwind CSS: btn이나 card 같은 컴포넌트 단위의 클래스를 제공하지 않습니다. 대신, flex, pt-4 (패딩 탑 16px), text-center, bg-blue-500 (파란색 배경)처럼 아주 작은 단위의 **"유틸리티 클래스"**들을 제공합니다. 이 유틸리티 클래스들은 각각 특정 CSS 속성(예: display: flex;, padding-top: 1rem;, text-align: center;, background-color: #3b82f6;)을 담당합니다.
- 주요 특징 및 장점
- 극강의 커스터마이징 유연성: 미리 정의된 디자인에 얽매이지 않고, 원하는 대로 모든 요소를 조합하여 유니크한 디자인을 만들 수 있습니다. 디자인 시스템을 구축하기에도 용이합니다.
- 빠른 개발 속도: HTML 파일에서 CSS를 왔다 갔다 할 필요 없이, 마크업 내에서 바로 스타일을 적용할 수 있어 개발 속도가 매우 빠릅니다. "CSS를 작성하기 위해 HTML 파일을 떠날 필요가 없다"는 것이 핵심 철학입니다.
- 반응형 디자인: md:, lg:와 같은 접두사를 사용하여 쉽게 반응형 디자인을 적용할 수 있습니다. 예를 들어 md:flex는 중간 화면 크기 이상에서만 display: flex;를 적용합니다.
- 작은 최종 CSS 파일 크기: 개발 과정에서는 많은 유틸리티 클래스가 존재하지만, 빌드 시에는 사용되지 않는 CSS를 자동으로 제거(Purge)해 주기 때문에 최종 결과물의 CSS 파일 크기가 매우 작아집니다. 이는 웹 성능에 긍정적인 영향을 줍니다.
- 컴포넌트 기반 프레임워크와의 시너지: React, Vue, Angular 등 컴포넌트 기반의 프레임워크와 함께 사용할 때 그 진가가 발휘됩니다. 컴포넌트 안에서 해당 컴포넌트의 스타일을 모두 관리할 수 있어 코드가 깔끔해집니다.
- 페이지 네이션
- 현재 표시되는 게시물 정보 (Showing X-Y of Z posts)
<div className="text-sm text-gray-600">
Showing {startIndex + 1}-{Math.min(endIndex, blogPosts.length)} of {blogPosts.length} posts
</div>
- '이전' 및 '다음' 버튼
- 페이지를 앞뒤로 이동할 수 있는 "Previous (이전)" 와 "Next (다음)" 버튼을 구현한 것입니다.
- onClick: 버튼이 클릭될 때 실행될 함수를 정의합니다.
- Previous 버튼: setCurrentPage(Math.max(1, currentPage - 1))
- Next 버튼: setCurrentPage(Math.min(totalPages, currentPage + 1))
<button
onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
disabled={currentPage === 1}
className="px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
>
Previous
</button>
<button
onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
disabled={currentPage === totalPages}
className="px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
>
Next
</button>
- 페이지 번호 목록
- 각 페이지 번호 버튼들을 동적으로 생성하고 렌더링하는 핵심적인 로직입니다.
- totalPages 값(예: 3)을 이용하여 길이가 totalPages인 배열을 만듭니다. Array.from의 두 번째 인자는 각 요소에 대해 실행될 맵핑 함수입니다.
- .map(page => (...)): 생성된 페이지 번호 배열을 순회하면서 각 페이지 번호에 해당하는 <button> 요소를 생성합니다.
<div className="flex items-center space-x-1">
{Array.from({ length: totalPages }, (_, i) => i + 1).map(page => (
<button
key={page}
onClick={() => setCurrentPage(page)}
className={`px-3 py-2 text-sm font-medium rounded-md ${
currentPage === page
? 'bg-blue-600 text-white'
: 'text-gray-500 bg-white border border-gray-300 hover:bg-gray-50'
}`}
>
{page}
</button>
))}
</div>
전체 코드 입니다.
1. BlogSlider.jsx
import React, { useState } from 'react'
function BlogSlider() {
const [currentPage, setCurrentPage] = useState(1)
const cardsPerPage = 5
const blogPosts = [
{
id: 1,
title: "Getting Started with React Three Fiber",
excerpt: "Learn how to create stunning 3D web experiences with React Three Fiber and Three.js. This comprehensive guide covers everything from basic setup to advanced animations.",
image: "https://images.unsplash.com/photo-1555066931-4365d14bab8c?w=400",
date: "2024-01-15",
author: "John Doe",
category: "Web Development",
readTime: "5 min read"
},
{
id: 2,
title: "Building Responsive Web Applications",
excerpt: "Discover the best practices for creating responsive web applications that work seamlessly across all devices and screen sizes.",
image: "https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=400",
date: "2024-01-12",
author: "Jane Smith",
category: "Frontend",
readTime: "8 min read"
},
{
id: 3,
title: "Modern JavaScript ES6+ Features",
excerpt: "Explore the latest JavaScript features including arrow functions, destructuring, async/await, and more to write cleaner, more efficient code.",
image: "https://images.unsplash.com/photo-1555066931-4365d14bab8c?w=400",
date: "2024-01-10",
author: "Mike Johnson",
category: "JavaScript",
readTime: "12 min read"
},
{
id: 4,
title: "Optimizing React Performance",
excerpt: "Learn advanced techniques to optimize your React applications for better performance, including memoization, code splitting, and lazy loading.",
image: "https://images.unsplash.com/photo-1633356122544-f134324a6cee?w=400",
date: "2024-01-08",
author: "Sarah Wilson",
category: "React",
readTime: "10 min read"
},
{
id: 5,
title: "CSS Grid vs Flexbox: When to Use What",
excerpt: "A comprehensive comparison of CSS Grid and Flexbox, with practical examples and guidelines for choosing the right layout method.",
image: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400",
date: "2024-01-05",
author: "Alex Brown",
category: "CSS",
readTime: "7 min read"
},
{
id: 6,
title: "Introduction to TypeScript",
excerpt: "Get started with TypeScript and learn how to add static typing to your JavaScript projects for better development experience.",
image: "https://images.unsplash.com/photo-1517077304055-6e89abbf09b0?w=400",
date: "2024-01-03",
author: "Chris Davis",
category: "TypeScript",
readTime: "15 min read"
},
{
id: 7,
title: "State Management with Redux Toolkit",
excerpt: "Learn how to manage application state effectively using Redux Toolkit, the official, opinionated way to write Redux logic.",
image: "https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=400",
date: "2024-01-01",
author: "Lisa Chen",
category: "Redux",
readTime: "20 min read"
},
{
id: 8,
title: "Building REST APIs with Node.js",
excerpt: "Create robust REST APIs using Node.js, Express, and MongoDB. Learn authentication, validation, and error handling.",
image: "https://images.unsplash.com/photo-1555066931-4365d14bab8c?w=400",
date: "2023-12-28",
author: "Tom Wilson",
category: "Backend",
readTime: "18 min read"
},
{
id: 9,
title: "Testing React Components with Jest",
excerpt: "Master the art of testing React components using Jest and React Testing Library for reliable, maintainable code.",
image: "https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=400",
date: "2023-12-25",
author: "Emma Taylor",
category: "Testing",
readTime: "14 min read"
},
{
id: 10,
title: "Deploying React Apps to Production",
excerpt: "Learn the best practices for deploying React applications to production, including optimization, CI/CD, and monitoring.",
image: "https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=400",
date: "2023-12-22",
author: "David Lee",
category: "Deployment",
readTime: "11 min read"
}
]
const totalPages = Math.ceil(blogPosts.length / cardsPerPage)
const startIndex = (currentPage - 1) * cardsPerPage
const endIndex = startIndex + cardsPerPage
const currentPosts = blogPosts.slice(startIndex, endIndex)
const formatDate = (dateString) => {
const date = new Date(dateString)
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
})
}
return (
<section className="py-20 bg-white">
<div className="max-w-7xl mx-auto px-6">
{/* 헤더 */}
<div className="text-center mb-12">
<h2 className="text-4xl font-bold text-gray-900 mb-4">
Latest Blog Posts
</h2>
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
Explore my latest thoughts on web development, programming tips, and industry insights
</p>
</div>
{/* 카드 그리드 */}
<div className="grid md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-6 mb-8">
{currentPosts.map(post => (
<div
key={post.id}
className="bg-white rounded-xl shadow-lg overflow-hidden hover:shadow-xl transition-shadow duration-300 border border-gray-100"
>
{/* 이미지 */}
<div className="relative h-48 overflow-hidden">
<img
src={post.image}
alt={post.title}
className="w-full h-full object-cover hover:scale-110 transition-transform duration-300"
/>
<div className="absolute top-4 left-4">
<span className="px-2 py-1 bg-blue-600 text-white text-xs rounded-full font-medium">
{post.category}
</span>
</div>
</div>
{/* 내용 */}
<div className="p-6">
<div className="flex items-center text-sm text-gray-500 mb-3">
<span>{formatDate(post.date)}</span>
<span className="mx-2">•</span>
<span>{post.readTime}</span>
</div>
<h3 className="text-lg font-bold text-gray-900 mb-3 line-clamp-2">
{post.title}
</h3>
<p className="text-gray-600 text-sm mb-4 line-clamp-3">
{post.excerpt}
</p>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-500">
By {post.author}
</span>
<button className="text-blue-600 hover:text-blue-700 text-sm font-medium">
Read More →
</button>
</div>
</div>
</div>
))}
</div>
{/* 페이지네이션 */}
<div className="flex items-center justify-between">
<div className="text-sm text-gray-600">
Showing {startIndex + 1}-{Math.min(endIndex, blogPosts.length)} of {blogPosts.length} posts
</div>
<div className="flex items-center space-x-2">
<button
onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
disabled={currentPage === 1}
className="px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
>
Previous
</button>
<div className="flex items-center space-x-1">
{Array.from({ length: totalPages }, (_, i) => i + 1).map(page => (
<button
key={page}
onClick={() => setCurrentPage(page)}
className={`px-3 py-2 text-sm font-medium rounded-md ${
currentPage === page
? 'bg-blue-600 text-white'
: 'text-gray-500 bg-white border border-gray-300 hover:bg-gray-50'
}`}
>
{page}
</button>
))}
</div>
<button
onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
disabled={currentPage === totalPages}
className="px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
>
Next
</button>
</div>
</div>
</div>
</section>
)
}
export default BlogSlider
2. App.jsx
import { motion, useScroll } from "motion/react";
import BlogSlider from "./components/BlogSlider";
function App() {
const { scrollYProgress } = useScroll();
return (
{" "}
<BlogSlider />
</div>
</>
);
}
export default App;
시연 사진입니다.
'React' 카테고리의 다른 글
[React] 홈페이지 메인화면에 배경 영상 추가하기 (2) | 2025.07.26 |
---|---|
[React] Tailwind CSS와 useState를 활용한 간단한 계산기 구현 (0) | 2025.07.24 |
[React] Motion 라이브러리 사용하여 스크롤 진행바 (scrollYProgress 사용) 애니메이션 구현하기 (6) | 2025.07.17 |
[React] Three Fiber 로 3D 효과 만들기 (0) | 2025.07.16 |
[React] react-scroll을 활용한 부드러운 앵커 스크롤 (Anchor Scroll) 구현하기 (0) | 2025.07.14 |