실습: 반응형 블로그 메인 페이지 만들기 완벽 가이드
현대 웹사이트에서 반응형 디자인은 선택이 아닌 필수입니다. 사용자들은 데스크톱, 태블릿, 모바일 등 다양한 기기에서 웹사이트를 방문하며, 모든 화면 크기에서 최적의 경험을 제공해야 합니다. 이 튜토리얼에서는 HTML, CSS, 약간의 JavaScript를 사용하여 모든 기기에 완벽하게 대응하는 블로그 메인 페이지를 처음부터 만들어보겠습니다.
이 실습을 통해 여러분은 반응형 웹 디자인의 핵심 원칙을 배우고, 실제 프로젝트에 적용할 수 있는 기술을 습득하게 될 것입니다. 코드를 직접 작성하며 따라오시면, 자신만의 반응형 블로그를 완성할 수 있습니다!
최종 결과물 미리보기
이 튜토리얼을 완료하면 아래와 같은 반응형 블로그 메인 페이지를 만들 수 있습니다:
- 데스크톱: 3단 레이아웃 (메인 콘텐츠 + 사이드바)
- 태블릿: 2단 레이아웃 (메인 콘텐츠 간략화 + 사이드바 하단으로)
- 모바일: 1단 레이아웃 (모든 요소가 세로로 나열)
주요 기능:
- 햄버거 메뉴 토글 (모바일)
- 블로그 포스트 카드 디자인
- 인기 포스트 및 카테고리 위젯
- 뉴스레터 구독 폼
- 다크 모드 전환 옵션
- 최적화된 로딩 성능
준비물 및 프로젝트 설정
필요한 도구
- 텍스트 에디터 (VS Code, Sublime Text, Atom 등)
- 웹 브라우저 (Chrome, Firefox, Edge 등)
- 기본적인 HTML, CSS, JavaScript 지식
프로젝트 폴더 구조 만들기
먼저, 프로젝트 폴더와 필요한 파일들을 생성합니다:
responsive-blog/
│
├── index.html
├── css/
│ ├── style.css
│ └── normalize.css
├── js/
│ └── script.js
└── images/
├── logo.svg
├── hero-bg.jpg
└── blog-posts/
├── post-1.jpg
├── post-2.jpg
└── post-3.jpg
normalize.css 추가하기
브라우저 간 일관된 스타일을 위해 normalize.css를 추가합니다. normalize.css에서 다운로드하거나 CDN을 사용할 수 있습니다.
HTML 기본 구조 작성
index.html
파일을 열고 기본 HTML 구조를 작성합니다:
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Responsive Blog</title>
<meta name="description" content="모든 기기에 최적화된 반응형 블로그 웹사이트">
<!-- CSS 파일 연결 -->
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/style.css">
<!-- 구글 폰트 -->
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap" rel="stylesheet">
<!-- 아이콘 라이브러리 (Font Awesome) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
</head>
<body>
<!-- 헤더 영역 -->
<header class="header">
<!-- 내비게이션 및 로고 영역 -->
</header>
<!-- 히어로 섹션 -->
<section class="hero">
<!-- 히어로 콘텐츠 -->
</section>
<!-- 메인 콘텐츠 -->
<main class="main-content">
<div class="container">
<div class="content-wrapper">
<!-- 블로그 포스트 영역 -->
<section class="blog-posts">
<!-- 블로그 포스트 카드들 -->
</section>
<!-- 사이드바 -->
<aside class="sidebar">
<!-- 위젯 영역 -->
</aside>
</div>
</div>
</main>
<!-- 푸터 영역 -->
<footer class="footer">
<!-- 푸터 콘텐츠 -->
</footer>
<!-- JavaScript 연결 -->
<script src="js/script.js"></script>
</body>
</html>
CSS 기본 스타일 설정
style.css
파일을 열고 기본 스타일을 설정합니다:
/* 기본 스타일 설정 */
:root {
/* 컬러 변수 */
--primary-color: #4a6bdf;
--secondary-color: #f8595d;
--dark-color: #333333;
--light-color: #f4f4f4;
--bg-color: #ffffff;
--text-color: #333333;
--gray-color: #666666;
--light-gray-color: #dddddd;
/* 타이포그래피 */
--font-family: 'Noto Sans KR', sans-serif;
--h1-size: 2.5rem;
--h2-size: 2rem;
--h3-size: 1.5rem;
--h4-size: 1.25rem;
--body-size: 1rem;
--small-size: 0.875rem;
/* 간격 */
--spacing-xs: 0.5rem;
--spacing-sm: 1rem;
--spacing-md: 1.5rem;
--spacing-lg: 2rem;
--spacing-xl: 3rem;
/* 테두리 */
--border-radius: 0.5rem;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-family);
font-size: var(--body-size);
line-height: 1.6;
color: var(--text-color);
background-color: var(--bg-color);
}
a {
text-decoration: none;
color: var(--primary-color);
transition: color 0.3s ease;
}
a:hover {
color: var(--secondary-color);
}
img {
max-width: 100%;
height: auto;
display: block;
}
ul {
list-style: none;
}
h1, h2, h3, h4, h5, h6 {
font-weight: 700;
line-height: 1.3;
margin-bottom: var(--spacing-sm);
}
h1 { font-size: var(--h1-size); }
h2 { font-size: var(--h2-size); }
h3 { font-size: var(--h3-size); }
h4 { font-size: var(--h4-size); }
p {
margin-bottom: var(--spacing-md);
}
button, .button {
display: inline-block;
background-color: var(--primary-color);
color: white;
padding: 0.75rem 1.5rem;
border: none;
border-radius: var(--border-radius);
font-family: var(--font-family);
font-size: var(--body-size);
cursor: pointer;
transition: background-color 0.3s ease;
}
button:hover, .button:hover {
background-color: var(--secondary-color);
}
/* 유틸리티 클래스 */
.container {
width: 90%;
max-width: 1200px;
margin: 0 auto;
padding: 0 var(--spacing-sm);
}
.text-center {
text-align: center;
}
.text-primary {
color: var(--primary-color);
}
.bg-light {
background-color: var(--light-color);
}
.mb-1 { margin-bottom: var(--spacing-xs); }
.mb-2 { margin-bottom: var(--spacing-sm); }
.mb-3 { margin-bottom: var(--spacing-md); }
.mb-4 { margin-bottom: var(--spacing-lg); }
.mb-5 { margin-bottom: var(--spacing-xl); }
헤더 및 내비게이션 구현
HTML 구조
헤더와 내비게이션 영역의 HTML을 작성합니다:
<header class="header">
<div class="container">
<nav class="navbar">
<div class="navbar-logo">
<a href="index.html">
<img src="images/logo.svg" alt="My Blog Logo" class="logo">
</a>
</div>
<button class="navbar-toggle" aria-label="메뉴 열기">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<div class="navbar-menu">
<ul class="navbar-nav">
<li class="nav-item active"><a href="#" class="nav-link">홈</a></li>
<li class="nav-item"><a href="#" class="nav-link">카테고리</a></li>
<li class="nav-item"><a href="#" class="nav-link">인기 포스트</a></li>
<li class="nav-item"><a href="#" class="nav-link">소개</a></li>
<li class="nav-item"><a href="#" class="nav-link">연락처</a></li>
</ul>
</div>
<div class="navbar-actions">
<button class="theme-toggle" aria-label="다크 모드 전환">
<i class="fas fa-moon"></i>
</button>
<div class="search-box">
<input type="text" placeholder="검색..." class="search-input">
<button class="search-button">
<i class="fas fa-search"></i>
</button>
</div>
</div>
</nav>
</div>
</header>
CSS 스타일링
헤더와 내비게이션의 CSS를 작성합니다:
/* 헤더 스타일 */
.header {
position: sticky;
top: 0;
z-index: 1000;
background-color: var(--bg-color);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 1rem 0;
}
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
}
.navbar-logo {
display: flex;
align-items: center;
}
.logo {
height: 40px;
}
.navbar-toggle {
display: none;
background: none;
border: none;
padding: 0.5rem;
cursor: pointer;
}
.icon-bar {
display: block;
width: 25px;
height: 3px;
margin: 5px auto;
background-color: var(--dark-color);
transition: all 0.3s;
}
.navbar-menu {
display: flex;
align-items: center;
}
.navbar-nav {
display: flex;
margin: 0;
padding: 0;
}
.nav-item {
margin: 0 1rem;
}
.nav-link {
color: var(--dark-color);
font-weight: 500;
padding: 0.5rem 0;
position: relative;
}
.nav-link::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 0;
height: 2px;
background-color: var(--primary-color);
transition: width 0.3s ease;
}
.nav-link:hover::after,
.nav-item.active .nav-link::after {
width: 100%;
}
.navbar-actions {
display: flex;
align-items: center;
}
.theme-toggle {
background: none;
border: none;
color: var(--dark-color);
font-size: 1.2rem;
margin-right: 1rem;
cursor: pointer;
padding: 0.5rem;
}
.search-box {
position: relative;
display: flex;
align-items: center;
}
.search-input {
padding: 0.5rem 2.5rem 0.5rem 1rem;
border: 1px solid var(--light-gray-color);
border-radius: 20px;
font-size: var(--small-size);
width: 180px;
transition: width 0.3s;
}
.search-input:focus {
outline: none;
width: 220px;
border-color: var(--primary-color);
}
.search-button {
position: absolute;
right: 0.5rem;
background: none;
border: none;
padding: 0.5rem;
color: var(--gray-color);
font-size: 0.9rem;
}
히어로 섹션 만들기
HTML 구조
히어로 섹션의 HTML을 작성합니다:
<section class="hero">
<div class="hero-overlay"></div>
<div class="container">
<div class="hero-content">
<span class="hero-subtitle">웹 개발 & 디자인</span>
<h1 class="hero-title">반응형 웹 디자인의 모든 것</h1>
<p class="hero-description">모바일부터 데스크톱까지 완벽하게 대응하는 반응형 웹사이트 제작 기법을 소개합니다.</p>
<div class="hero-buttons">
<a href="#" class="button">최신 포스트 보기</a>
<a href="#" class="button button-outline">구독하기</a>
</div>
</div>
</div>
</section>
CSS 스타일링
히어로 섹션의 CSS를 작성합니다:
/* 히어로 섹션 스타일 */
.hero {
position: relative;
height: 500px;
background-image: url('../images/hero-bg.jpg');
background-size: cover;
background-position: center;
display: flex;
align-items: center;
color: white;
text-align: center;
margin-bottom: var(--spacing-xl);
}
.hero-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
z-index: 1;
}
.hero-content {
position: relative;
z-index: 2;
max-width: 800px;
margin: 0 auto;
}
.hero-subtitle {
display: inline-block;
background-color: var(--primary-color);
color: white;
padding: 0.3rem 1rem;
border-radius: 20px;
font-size: 0.9rem;
font-weight: 500;
margin-bottom: var(--spacing-sm);
}
.hero-title {
font-size: 3rem;
margin-bottom: var(--spacing-md);
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.hero-description {
font-size: 1.2rem;
margin-bottom: var(--spacing-lg);
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
.hero-buttons {
display: flex;
justify-content: center;
gap: 1rem;
}
.button-outline {
background-color: transparent;
border: 2px solid white;
color: white;
}
.button-outline:hover {
background-color: white;
color: var(--primary-color);
}
블로그 포스트 그리드 레이아웃
HTML 구조
블로그 포스트 섹션의 HTML을 작성합니다:
<section class="blog-posts">
<h2 class="section-title">최신 포스트</h2>
<div class="post-grid">
<!-- 포스트 1 -->
<article class="post-card">
<div class="post-card-image">
<img src="images/blog-posts/post-1.jpg" alt="반응형 디자인 기초">
</div>
<div class="post-card-content">
<div class="post-card-category">웹 디자인</div>
<h3 class="post-card-title">
<a href="#">반응형 디자인의 기본 원칙: 모바일 우선 접근법</a>
</h3>
<p class="post-card-excerpt">
반응형 웹 디자인에서 모바일 우선 접근법이 중요한 이유와 실제 적용 방법에 대해 알아봅니다.
</p>
<div class="post-card-meta">
<div class="post-card-date">
<i class="far fa-calendar"></i> 2023년 5월 10일
</div>
<div class="post-card-author">
<i class="far fa-user"></i> 홍길동
</div>
</div>
</div>
</article>
<!-- 포스트 2 -->
<article class="post-card">
<div class="post-card-image">
<img src="images/blog-posts/post-2.jpg" alt="CSS 그리드 레이아웃">
</div>
<div class="post-card-content">
<div class="post-card-category">CSS</div>
<h3 class="post-card-title">
<a href="#">CSS 그리드로 복잡한 레이아웃 쉽게 구현하기</a>
</h3>
<p class="post-card-excerpt">
CSS 그리드 레이아웃을 활용하여 복잡한 디자인도 쉽게 구현하는 방법을 단계별로 설명합니다.
</p>
<div class="post-card-meta">
<div class="post-card-date">
<i class="far fa-calendar"></i> 2023년 5월 5일
</div>
<div class="post-card-author">
<i class="far fa-user"></i> 김철수
</div>
</div>
</div>
</article>
<!-- 포스트 3 -->
<article class="post-card">
<div class="post-card-image">
<img src="images/blog-posts/post-3.jpg" alt="JavaScript 팁">
</div>
<div class="post-card-content">
<div class="post-card-category">JavaScript</div>
<h3 class="post-card-title">
<a href="#">실무에서 바로 적용하는 JavaScript 핵심 팁 10가지</a>
</h3>
<p class="post-card-excerpt">
현직 개발자들이 실무에서 자주 사용하는 유용한 JavaScript 팁 10가지를 소개합니다.
</p>
<div class="post-card-meta">
<div class="post-card-date">
<i class="far fa-calendar"></i> 2023년 4월 28일
</div>
<div class="post-card-author">
<i class="far fa-user"></i> 이영희
</div>
</div>
</div>
</article>
<!-- 더 많은 포스트 카드 추가 -->
<!-- 포스트 4-6 같은 형식으로 반복 -->
</div>
<div class="pagination">
<a href="#" class="page-link active">1</a>
<a href="#" class="page-link">2</a>
<a href="#" class="page-link">3</a>
<a href="#" class="page-link next">
<i class="fas fa-arrow-right"></i>
</a>
</div>
</section>
CSS 스타일링
블로그 포스트 섹션의 CSS를 작성합니다:
/* 메인 콘텐츠 레이아웃 */
.main-content {
padding: var(--spacing-lg) 0;
}
.content-wrapper {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 2rem;
}
/* 섹션 타이틀 */
.section-title {
margin-bottom: var(--spacing-lg);
position: relative;
padding-bottom: 0.5rem;
}
.section-title::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 60px;
height: 3px;
background-color: var(--primary-color);
}
/* 블로그 포스트 그리드 */
.post-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 2rem;
margin-bottom: var(--spacing-xl);
}
.post-card {
border-radius: var(--border-radius);
overflow: hidden;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
transition: transform 0.3s, box-shadow 0.3s;
background-color: var(--bg-color);
}
.post-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15);
}
.post-card-image {
height: 200px;
overflow: hidden;
}
.post-card-image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.5s;
}
.post-card:hover .post-card-image img {
transform: scale(1.1);
}
.post-card-content {
padding: var(--spacing-md);
}
.post-card-category {
display: inline-block;
background-color: var(--primary-color);
color: white;
font-size: 0.8rem;
padding: 0.2rem 0.8rem;
border-radius: 20px;
margin-bottom: 0.5rem;
}
.post-card-title {
font-size: 1.3rem;
margin-bottom: 0.5rem;
line-height: 1.4;
}
.post-card-title a {
color: var(--dark-color);
transition: color 0.3s;
}
.post-card-title a:hover {
color: var(--primary-color);
}
.post-card-excerpt {
font-size: 0.95rem;
color: var(--gray-color);
margin-bottom: var(--spacing-sm);
line-height: 1.6;
}
.post-card-meta {
display: flex;
justify-content: space-between;
font-size: 0.85rem;
color: var(--gray-color);
}
.post-card-date, .post-card-author {
display: flex;
align-items: center;
}
.post-card-meta i {
margin-right: 0.3rem;
}
/* 페이지네이션 */
.pagination {
display: flex;
justify-content: center;
gap: 0.5rem;
}
.page-link {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: 50%;
background-color: var(--light-color);
color: var(--dark-color);
font-weight: 500;
transition: all 0.3s;
}
.page-link:hover, .page-link.active {
background-color: var(--primary-color);
color: white;
}
.page-link.next {
background-color: var(--dark-color);
color: white;
}
사이드바 및 위젯 영역
HTML 구조
사이드바와 위젯 영역의 HTML을 작성합니다:
<aside class="sidebar">
<!-- 프로필 위젯 -->
<div class="widget widget-profile">
<div class="widget-profile-image">
<img src="images/profile.jpg" alt="블로그 운영자 프로필">
</div>
<h3 class="widget-profile-name">홍길동</h3>
<p class="widget-profile-bio">
프론트엔드 개발자이자 UI/UX 디자이너입니다. 모던 웹 기술과 디자인 트렌드에 관심이 많습니다.
</p>
<div class="widget-profile-social">
<a href="#" class="social-link"><i class="fab fa-twitter"></i></a>
<a href="#" class="social-link"><i class="fab fa-facebook"></i></a>
<a href="#" class="social-link"><i class="fab fa-instagram"></i></a>
<a href="#" class="social-link"><i class="fab fa-github"></i></a>
</div>
</div>
<!-- 인기 포스트 위젯 -->
<div class="widget widget-popular">
<h3 class="widget-title">인기 포스트</h3>
<ul class="popular-posts">
<li class="popular-post-item">
<a href="#" class="popular-post-link">
<div class="popular-post-image">
<img src="images/blog-posts/popular-1.jpg" alt="인기 포스트 1">
</div>
<div class="popular-post-content">
<h4 class="popular-post-title">프론트엔드 개발자를 위한 필수 도구</h4>
<span class="popular-post-date">2023년 5월 1일</span>
</div>
</a>
</li>
<li class="popular-post-item">
<a href="#" class="popular-post-link">
<div class="popular-post-image">
<img src="images/blog-posts/popular-2.jpg" alt="인기 포스트 2">
</div>
<div class="popular-post-content">
<h4 class="popular-post-title">웹 성능 최적화 방법 10가지</h4>
<span class="popular-post-date">2023년 4월 25일</span>
</div>
</a>
</li>
<li class="popular-post-item">
<a href="#" class="popular-post-link">
<div class="popular-post-image">
<img src="images/blog-posts/popular-3.jpg" alt="인기 포스트 3">
</div>
<div class="popular-post-content">
<h4 class="popular-post-title">CSS 변수 활용 가이드</h4>
<span class="popular-post-date">2023년 4월 20일</span>
</div>
</a>
</li>
</ul>
</div>
<!-- 카테고리 위젯 -->
<div class="widget widget-categories">
<h3 class="widget-title">카테고리</h3>
<ul class="category-list">
<li class="category-item">
<a href="#" class="category-link">HTML & CSS <span class="category-count">12</span></a>
</li>
<li class="category-item">
<a href="#" class="category-link">JavaScript <span class="category-count">8</span></a>
</li>
<li class="category-item">
<a href="#" class="category-link">반응형 디자인 <span class="category-count">6</span></a>
</li>
<li class="category-item">
<a href="#" class="category-link">성능 최적화 <span class="category-count">4</span></a>
</li>
<li class="category-item">
<a href="#" class="category-link">UI/UX 디자인 <span class="category-count">7</span></a>
</li>
</ul>
</div>
<!-- 뉴스레터 구독 위젯 -->
<div class="widget widget-newsletter">
<h3 class="widget-title">뉴스레터 구독</h3>
<p class="widget-description">
최신 웹 개발 및 디자인 트렌드를 이메일로 받아보세요.
</p>
<form class="newsletter-form">
<input type="email" placeholder="이메일 주소" class="newsletter-input" required>
<button type="submit" class="newsletter-button">구독하기</button>
</form>
</div>
<!-- 태그 클라우드 위젯 -->
<div class="widget widget-tags">
<h3 class="widget-title">태그 클라우드</h3>
<div class="tag-cloud">
<a href="#" class="tag">HTML</a>
<a href="#" class="tag">CSS</a>
<a href="#" class="tag">JavaScript</a>
<a href="#" class="tag">React</a>
<a href="#" class="tag">Vue.js</a>
<a href="#" class="tag">Responsive</a>
<a href="#" class="tag">Mobile</a>
<a href="#" class="tag">Performance</a>
<a href="#" class="tag">UX</a>
<a href="#" class="tag">Design</a>
</div>
</div>
</aside>
CSS 스타일링
사이드바와 위젯 영역의 CSS를 작성합니다:
/* 사이드바 및 위젯 스타일 */
.sidebar {
position: sticky;
top: 100px;
}
.widget {
background-color: var(--bg-color);
border-radius: var(--border-radius);
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
padding: var(--spacing-md);
margin-bottom: var(--spacing-lg);
}
.widget-title {
font-size: 1.3rem;
margin-bottom: var(--spacing-md);
position: relative;
padding-bottom: 0.5rem;
}
.widget-title::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 40px;
height: 3px;
background-color: var(--primary-color);
}
/* 프로필 위젯 */
.widget-profile {
text-align: center;
}
.widget-profile-image {
width: 120px;
height: 120px;
border-radius: 50%;
overflow: hidden;
margin: 0 auto var(--spacing-sm);
border: 3px solid var(--light-color);
}
.widget-profile-name {
font-size: 1.3rem;
margin-bottom: var(--spacing-xs);
}
.widget-profile-bio {
font-size: 0.95rem;
color: var(--gray-color);
margin-bottom: var(--spacing-sm);
}
.widget-profile-social {
display: flex;
justify-content: center;
gap: 0.8rem;
}
.social-link {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 50%;
background-color: var(--light-color);
color: var(--gray-color);
transition: all 0.3s;
}
.social-link:hover {
background-color: var(--primary-color);
color: white;
}
/* 인기 포스트 위젯 */
.popular-posts {
display: flex;
flex-direction: column;
gap: 1rem;
}
.popular-post-item {
border-bottom: 1px solid var(--light-gray-color);
padding-bottom: 1rem;
}
.popular-post-item:last-child {
border-bottom: none;
padding-bottom: 0;
}
.popular-post-link {
display: flex;
gap: 1rem;
color: var(--dark-color);
}
.popular-post-image {
flex-shrink: 0;
width: 80px;
height: 60px;
border-radius: 4px;
overflow: hidden;
}
.popular-post-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.popular-post-title {
font-size: 0.95rem;
margin-bottom: 0.3rem;
transition: color 0.3s;
}
.popular-post-link:hover .popular-post-title {
color: var(--primary-color);
}
.popular-post-date {
font-size: 0.8rem;
color: var(--gray-color);
}
/* 카테고리 위젯 */
.category-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.category-link {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.7rem 1rem;
background-color: var(--light-color);
border-radius: 4px;
color: var(--dark-color);
transition: all 0.3s;
}
.category-link:hover {
background-color: var(--primary-color);
color: white;
}
.category-count {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border-radius: 50%;
background-color: white;
color: var(--dark-color);
font-size: 0.8rem;
font-weight: 500;
}
/* 뉴스레터 위젯 */
.widget-description {
font-size: 0.95rem;
color: var(--gray-color);
margin-bottom: var(--spacing-sm);
}
.newsletter-form {
display: flex;
flex-direction: column;
gap: 0.8rem;
}
.newsletter-input {
padding: 0.8rem 1rem;
border: 1px solid var(--light-gray-color);
border-radius: 4px;
font-family: var(--font-family);
font-size: 0.95rem;
}
.newsletter-input:focus {
outline: none;
border-color: var(--primary-color);
}
.newsletter-button {
width: 100%;
}
/* 태그 클라우드 위젯 */
.tag-cloud {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.tag {
display: inline-block;
padding: 0.3rem 0.8rem;
background-color: var(--light-color);
border-radius: 4px;
color: var(--gray-color);
font-size: 0.85rem;
transition: all 0.3s;
}
.tag:hover {
background-color: var(--primary-color);
color: white;
}
푸터 영역 디자인
HTML 구조
푸터 영역의 HTML을 작성합니다:
<footer class="footer">
<div class="container">
<div class="footer-content">
<div class="footer-logo">
<img src="images/logo-white.svg" alt="My Blog Logo" class="logo">
<p class="footer-description">
웹 개발과 디자인에 관한 유용한 정보를 제공하는 블로그입니다.
</p>
</div>
<div class="footer-links">
<h3 class="footer-title">바로가기</h3>
<ul class="footer-nav">
<li><a href="#">홈</a></li>
<li><a href="#">블로그</a></li>
<li><a href="#">카테고리</a></li>
<li><a href="#">소개</a></li>
<li><a href="#">연락처</a></li>
</ul>
</div>
<div class="footer-categories">
<h3 class="footer-title">카테고리</h3>
<ul class="footer-nav">
<li><a href="#">HTML & CSS</a></li>
<li><a href="#">JavaScript</a></li>
<li><a href="#">반응형 디자인</a></li>
<li><a href="#">성능 최적화</a></li>
<li><a href="#">UI/UX 디자인</a></li>
</ul>
</div>
<div class="footer-subscribe">
<h3 class="footer-title">뉴스레터 구독</h3>
<p class="footer-description">
최신 글과 업데이트 소식을 받아보세요.
</p>
<form class="footer-form">
<input type="email" placeholder="이메일 주소" class="footer-input" required>
<button type="submit" class="footer-button">구독</button>
</form>
</div>
</div>
<div class="footer-bottom">
<div class="copyright">
© 2023 My Blog. All Rights Reserved.
</div>
<div class="footer-social">
<a href="#" class="social-link"><i class="fab fa-twitter"></i></a>
<a href="#" class="social-link"><i class="fab fa-facebook"></i></a>
<a href="#" class="social-link"><i class="fab fa-instagram"></i></a>
<a href="#" class="social-link"><i class="fab fa-github"></i></a>
</div>
</div>
</div>
</footer>
CSS 스타일링
푸터 영역의 CSS를 작성합니다:
/* 푸터 스타일 */
.footer {
background-color: var(--dark-color);
color: white;
padding: var(--spacing-xl) 0 var(--spacing-md);
margin-top: var(--spacing-xl);
}
.footer-content {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 2rem;
margin-bottom: var(--spacing-lg);
}
.footer-logo .logo {
height: 40px;
margin-bottom: var(--spacing-sm);
}
.footer-description {
font-size: 0.95rem;
color: rgba(255, 255, 255, 0.7);
line-height: 1.6;
}
.footer-title {
font-size: 1.1rem;
margin-bottom: var(--spacing-md);
position: relative;
padding-bottom: 0.5rem;
}
.footer-title::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 30px;
height: 2px;
background-color: var(--primary-color);
}
.footer-nav {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.footer-nav a {
color: rgba(255, 255, 255, 0.7);
font-size: 0.95rem;
transition: color 0.3s;
}
.footer-nav a:hover {
color: white;
}
.footer-form {
display: flex;
margin-top: var(--spacing-sm);
}
.footer-input {
flex: 1;
padding: 0.8rem 1rem;
border: none;
border-radius: 4px 0 0 4px;
font-family: var(--font-family);
font-size: 0.95rem;
}
.footer-button {
padding: 0 1.5rem;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 0 4px 4px 0;
cursor: pointer;
transition: background-color 0.3s;
}
.footer-button:hover {
background-color: var(--secondary-color);
}
.footer-bottom {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: var(--spacing-md);
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.copyright {
font-size: 0.9rem;
color: rgba(255, 255, 255, 0.7);
}
.footer-social {
display: flex;
gap: 1rem;
}
.footer-social .social-link {
background-color: rgba(255, 255, 255, 0.1);
color: white;
}
.footer-social .social-link:hover {
background-color: var(--primary-color);
}
미디어 쿼리로 반응형 디자인 구현
이제 다양한 화면 크기에 대응하는 미디어 쿼리를 작성합니다:
/* 반응형 디자인을 위한 미디어 쿼리 */
/* 태블릿 (768px ~ 1023px) */
@media (max-width: 1023px) {
:root {
--h1-size: 2.2rem;
--h2-size: 1.8rem;
--h3-size: 1.4rem;
--h4-size: 1.2rem;
}
.hero-title {
font-size: 2.5rem;
}
.content-wrapper {
grid-template-columns: 1fr;
gap: 3rem;
}
.sidebar {
position: static;
top: auto;
}
.post-grid {
grid-template-columns: repeat(2, 1fr);
}
.footer-content {
grid-template-columns: repeat(2, 1fr);
}
}
/* 모바일 (767px 이하) */
@media (max-width: 767px) {
:root {
--h1-size: 2rem;
--h2-size: 1.6rem;
--h3-size: 1.3rem;
--h4-size: 1.1rem;
--spacing-xl: 2.5rem;
--spacing-lg: 1.5rem;
}
.navbar {
flex-wrap: nowrap;
}
.navbar-toggle {
display: block;
order: 3;
}
.navbar-menu {
position: fixed;
top: 70px;
left: 0;
width: 100%;
height: 0;
background-color: var(--bg-color);
overflow: hidden;
transition: height 0.3s ease;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
flex-direction: column;
z-index: 1000;
}
.navbar-menu.active {
height: auto;
padding: 1rem 0;
}
.navbar-nav {
flex-direction: column;
width: 100%;
text-align: center;
}
.nav-item {
margin: 0.8rem 0;
}
.search-box {
display: none;
}
.hero {
height: 400px;
}
.hero-title {
font-size: 2rem;
}
.hero-description {
font-size: 1rem;
}
.hero-buttons {
flex-direction: column;
gap: 0.8rem;
}
.post-grid {
grid-template-columns: 1fr;
}
.footer-content {
grid-template-columns: 1fr;
}
.footer-bottom {
flex-direction: column;
gap: 1rem;
text-align: center;
}
}
/* 작은 모바일 (480px 이하) */
@media (max-width: 480px) {
.container {
width: 95%;
}
.hero-title {
font-size: 1.8rem;
}
.pagination {
flex-wrap: wrap;
}
}
JavaScript 기능 추가
이제 필요한 JavaScript 기능을 추가합니다. script.js
파일을 열고 다음 코드를 작성합니다:
document.addEventListener('DOMContentLoaded', function() {
// 모바일 메뉴 토글
const navbarToggle = document.querySelector('.navbar-toggle');
const navbarMenu = document.querySelector('.navbar-menu');
navbarToggle.addEventListener('click', function() {
navbarMenu.classList.toggle('active');
// 햄버거 아이콘 애니메이션
this.classList.toggle('active');
if (this.classList.contains('active')) {
// X 모양으로 변경
this.querySelector('.icon-bar:nth-child(1)').style.transform = 'translateY(8px) rotate(45deg)';
this.querySelector('.icon-bar:nth-child(2)').style.opacity = '0';
this.querySelector('.icon-bar:nth-child(3)').style.transform = 'translateY(-8px) rotate(-45deg)';
} else {
// 원래 햄버거 모양으로 복원
this.querySelector('.icon-bar:nth-child(1)').style.transform = 'none';
this.querySelector('.icon-bar:nth-child(2)').style.opacity = '1';
this.querySelector('.icon-bar:nth-child(3)').style.transform = 'none';
}
});
// 스크롤 시 헤더 스타일 변경
const header = document.querySelector('.header');
window.addEventListener('scroll', function() {
if (window.scrollY > 50) {
header.classList.add('scrolled');
} else {
header.classList.remove('scrolled');
}
});
// 뉴스레터 폼 제출
const newsletterForm = document.querySelector('.newsletter-form');
if (newsletterForm) {
newsletterForm.addEventListener('submit', function(e) {
e.preventDefault();
const emailInput = this.querySelector('input[type="email"]');
const email = emailInput.value.trim();
if (email) {
// 실제 구현에서는 서버로 데이터 전송
alert('뉴스레터 구독 신청이 완료되었습니다!');
emailInput.value = '';
}
});
}
// 푸터 폼 제출
const footerForm = document.querySelector('.footer-form');
if (footerForm) {
footerForm.addEventListener('submit', function(e) {
e.preventDefault();
const emailInput = this.querySelector('input[type="email"]');
const email = emailInput.value.trim();
if (email) {
// 실제 구현에서는 서버로 데이터 전송
alert('뉴스레터 구독 신청이 완료되었습니다!');
emailInput.value = '';
}
});
}
});
헤더 스타일 변경을 위한 CSS를 추가합니다:
/* 스크롤 시 헤더 스타일 */
.header.scrolled {
background-color: rgba(255, 255, 255, 0.95);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 0.5rem 0;
transition: all 0.3s;
}
다크 모드 구현
이제 다크 모드 기능을 추가합니다. 먼저 CSS 변수에 다크 모드 스타일을 추가합니다:
:root {
/* 라이트 모드 (기본) */
--primary-color: #4a6bdf;
--secondary-color: #f8595d;
--dark-color: #333333;
--light-color: #f4f4f4;
--bg-color: #ffffff;
--text-color: #333333;
--gray-color: #666666;
--light-gray-color: #dddddd;
/* 기타 변수들... */
}
/* 다크 모드 */
[data-theme="dark"] {
--primary-color: #5d7ce7;
--secondary-color: #ff6b6e;
--dark-color: #f4f4f4;
--light-color: #444444;
--bg-color: #121212;
--text-color: #f4f4f4;
--gray-color: #aaaaaa;
--light-gray-color: #444444;
}
JavaScript에 다크 모드 전환 기능을 추가합니다:
// 다크 모드 토글
const themeToggle = document.querySelector('.theme-toggle');
// 선호 색 테마 감지
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
const currentTheme = localStorage.getItem('theme');
// 저장된 테마가 있거나 사용자가 다크 모드를 선호하는 경우
if (currentTheme === 'dark' || (!currentTheme && prefersDarkScheme.matches)) {
document.documentElement.setAttribute('data-theme', 'dark');
themeToggle.innerHTML = '<i class="fas fa-sun"></i>';
} else {
document.documentElement.setAttribute('data-theme', 'light');
themeToggle.innerHTML = '<i class="fas fa-moon"></i>';
}
themeToggle.addEventListener('click', function() {
const currentTheme = document.documentElement.getAttribute('data-theme');
if (currentTheme === 'light') {
document.documentElement.setAttribute('data-theme', 'dark');
localStorage.setItem('theme', 'dark');
this.innerHTML = '<i class="fas fa-sun"></i>';
} else {
document.documentElement.setAttribute('data-theme', 'light');
localStorage.setItem('theme', 'light');
this.innerHTML = '<i class="fas fa-moon"></i>';
}
});
성능 최적화 팁
반응형 블로그 페이지의 성능을 최적화하기 위한 몇 가지 팁:
- 이미지 최적화: 모든 이미지를 압축하고 적절한 크기로 조정합니다. WebP 형식의 이미지를 사용하면 좋습니다.
- 지연 로딩: 이미지와 비디오에 지연 로딩을 적용합니다:
<img src="placeholder.jpg" data-src="actual-image.jpg" class="lazy" alt="이미지 설명">
// 이미지 지연 로딩
document.addEventListener('DOMContentLoaded', function() {
const lazyImages = [].slice.call(document.querySelectorAll('img.lazy'));
if ('IntersectionObserver' in window) {
let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
let lazyImage = entry.target;
lazyImage.src = lazyImage.dataset.src;
lazyImage.classList.remove('lazy');
lazyImageObserver.unobserve(lazyImage);
}
});
});
lazyImages.forEach(function(lazyImage) {
lazyImageObserver.observe(lazyImage);
});
}
});
- CSS 최적화: 사용하지 않는 CSS를 제거하고, 중복되는 스타일을 통합합니다.
- 폰트 최적화: 필요한 폰트 웨이트만 로드하고, font-display 속성을 사용합니다:
@font-face {
font-family: 'Noto Sans KR';
src: url('noto-sans-kr.woff2') format('woff2');
font-weight: 400;
font-display: swap;
}
- 스크립트 최적화: JavaScript 파일은 가능한 작게 유지하고, 필요한 경우 비동기적으로 로드합니다:
<script src="js/script.js" async></script>
마무리 및 다음 단계
축하합니다! 이제 완전히 반응형인 블로그 메인 페이지를 만들었습니다. 이 페이지는 모바일, 태블릿, 데스크톱 등 모든 화면 크기에서 잘 작동합니다.
다음 단계로 도전해볼 만한 것들:
- 블로그 상세 페이지 만들기: 글 내용, 댓글 섹션, 관련 글 등을 포함한 상세 페이지를 만듭니다.
- 카테고리 페이지 구현: 특정 카테고리에 속한 글들만 보여주는 페이지를 만듭니다.
- 검색 기능 추가: 사용자가 원하는 내용을 검색할 수 있는 기능을 추가합니다.
- 소셜 공유 버튼 추가: 각 글을 소셜 미디어에 공유할 수 있는 버튼을 추가합니다.
- 댓글 시스템 구현: 사용자가 블로그 글에 댓글을 남길 수 있는 기능을 구현합니다.
- 백엔드 연동: 실제 블로그 데이터를 관리할 수 있는 백엔드 시스템과 연동합니다.
반응형 웹 디자인은 현대 웹 개발의 필수 요소입니다. 이 튜토리얼을 통해 반응형 디자인의 기본 원칙을 배우고, 실제 프로젝트에 적용하는 방법을 알아보았습니다. 더 발전된 기능이나 스타일링에 도전해보며, 여러분만의 블로그를 완성해보세요!
질문이나 의견이 있으시면 댓글로 남겨주세요. 행복한 코딩 되세요! 🚀