728x90
인기있는 유튜브나 넷플릭스의 데이터들은 어떻게 표시가 될까요?
# 넷플릭스, 유튜브와 같은 서비스들은 많은 콘텐츠를 가로 스크롤이 가능한 카드 레이아웃으로 표시되며
웹페이지에서 콘텐츠를 효율적으로 보여주기 위해 카드 형태의 레이아웃이 자주 사용됩니다.
## 구현 목적
- 제한된 화면 공간에서 많은 데이터를 효율적으로 표시
- 사용자가 직관적으로 콘텐츠를 탐색할 수 있는 UI 제공
- 마우스 드래그로 자연스러운 스크롤 인터랙션 구현
## 주요 기능
- 카드 형태의 데이터 레이아웃 구성
- 가로 스크롤 기능
- 마우스 드래그로 슬라이드 제어
HTML
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SportSpot</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="../css/main5.css">
</head>
<body>
<!-- 헤더 -->
<header class="header">
<div class="search-bar">
<div class="logo">SportSpot</div>
<input type="text" class="search-input" placeholder="원하시는 운동이나 지역, 시설을 검색해보세요">
</div>
</header>
<!-- 지도 섹션 -->
<section class="map-section">
<!-- 카카오맵이 들어갈 자리 -->
</section>
<!-- 연령별 추천 프로그램 -->
<section class="program-section">
<h2 class="section-title">
<span class="material-icons">people</span>
연령별 추천 프로그램
</h2>
<!-- 청소년 추천 -->
<h3>청소년 추천</h3>
<div class="program-slider">
<div class="program-card">
<span class="program-type type-youth">청소년</span>
<h4>청소년 수영교실</h4>
<p class="price">50,000원</p>
<p class="facility-info">구립스포츠센터</p>
<p class="facility-info">평일 16:00-17:00</p>
</div>
<!-- 추가 카드들 -->
</div>
<!-- 성인 추천 -->
<h3>성인 추천</h3>
<div class="program-slider">
<div class="program-card">
<span class="program-type type-adult">성인</span>
<h4>아침 휘트니스</h4>
<p class="price">120,000원</p>
<p class="facility-info">피트니스센터</p>
<p class="facility-info">평일 07:00-08:00</p>
</div>
<!-- 추가 카드들 -->
</div>
<!-- 시니어 추천 -->
<h3>시니어 추천</h3>
<div class="program-slider">
<div class="program-card">
<span class="program-type type-senior">시니어</span>
<h4>실버 요가</h4>
<p class="price">60,000원</p>
<p class="facility-info">실버복지관</p>
<p class="facility-info">평일 10:00-11:00</p>
</div>
<!-- 추가 카드들 -->
</div>
</section>
<!-- 종목별 추천 프로그램 -->
<section class="program-section">
<h2 class="section-title">
<span class="material-icons">sports</span>
종목별 추천 프로그램
</h2>
<!-- 구기 스포츠 -->
<h3>구기 스포츠</h3>
<div class="program-slider">
<div class="program-card">
<span class="program-type type-ball">구기</span>
<h4>축구 기초반</h4>
<p class="price">70,000원</p>
<p class="facility-info">시립체육관</p>
<p class="facility-info">화목 17:00-18:30</p>
</div>
<!-- 추가 카드들 -->
</div>
<!-- 수상 스포츠 -->
<h3>수상 스포츠</h3>
<div class="program-slider">
<!-- 프로그램 카드들 -->
</div>
<!-- 피트니스 -->
<h3>피트니스</h3>
<div class="program-slider">
<!-- 프로그램 카드들 -->
</div>
</section>
<script src="../js/main5.js"></script>
</body>
</html>
js
// 프로그램 데이터
const programData = {
youth: [
{
type: "청소년",
title: "청소년 수영교실",
price: "50,000원",
facility: "구립스포츠센터",
time: "평일 16:00-17:00",
icon: "pool"
},
{
type: "청소년",
title: "농구 기초반",
price: "70,000원",
facility: "시립체육관",
time: "화목 17:00-18:00",
icon: "sports_basketball"
},
{
type: "청소년",
title: "우량 배드민턴",
price: "65,000원",
facility: "종합체육센터",
time: "평일 15:00-16:00",
icon: "sports_tennis"
},
{
type: "청소년",
title: "탁구 심화반",
price: "60,000원",
facility: "청소년스포츠센터",
time: "평일 18:00-19:00",
icon: "sports"
},
{
type: "청소년",
title: "주말 축구교실",
price: "80,000원",
facility: "축구장",
time: "토일 09:00-11:00",
icon: "sports_soccer"
},
{
type: "청소년",
title: "방과후 태권도",
price: "75,000원",
facility: "태권도장",
time: "평일 15:30-17:00",
icon: "sports_martial_arts"
},
{
type: "청소년",
title: "청소년 요가",
price: "55,000원",
facility: "청소년센터",
time: "화목 16:00-17:00",
icon: "self_improvement"
}
],
adult: [
{
type: "성인",
title: "아침 휘트니스",
price: "120,000원",
facility: "피트니스센터",
time: "평일 07:00-08:00",
icon: "fitness_center"
},
{
type: "성인",
title: "저녁 필라테스",
price: "100,000원",
facility: "필라테스스튜디오",
time: "평일 19:00-20:30",
icon: "self_improvement"
},
{
type: "성인",
title: "크로스핏 기초반",
price: "150,000원",
facility: "크로스핏센터",
time: "평일 20:00-21:00",
icon: "fitness_center"
},
{
type: "성인",
title: "직장인 수영반",
price: "90,000원",
facility: "수영장",
time: "평일 19:00-20:00",
icon: "pool"
},
{
type: "성인",
title: "주말 테니스",
price: "110,000원",
facility: "테니스장",
time: "토일 10:00-12:00",
icon: "sports_tennis"
},
{
type: "성인",
title: "복싱 클래스",
price: "130,000원",
facility: "복싱짐",
time: "평일 18:00-19:30",
icon: "sports_mma"
},
{
type: "성인",
title: "아침 요가",
price: "95,000원",
facility: "요가스튜디오",
time: "평일 06:30-07:30",
icon: "self_improvement"
},
{
type: "성인",
title: "저녁 줌바댄스",
price: "85,000원",
facility: "댄스스튜디오",
time: "화목 19:30-20:30",
icon: "music_note"
}
],
ballSports: [
{
type: "구기",
title: "축구 기초반",
price: "70,000원",
facility: "시립체육관",
time: "화목 17:00-18:30",
icon: "sports_soccer"
},
{
type: "구기",
title: "농구 교실",
price: "75,000원",
facility: "종합체육관",
time: "월수금 19:00-20:30",
icon: "sports_basketball"
},
{
type: "구기",
title: "배구 입문반",
price: "65,000원",
facility: "체육문화센터",
time: "화목 19:00-20:30",
icon: "sports_volleyball"
},
{
type: "구기",
title: "주말 풋살",
price: "80,000원",
facility: "풋살장",
time: "토일 14:00-16:00",
icon: "sports_soccer"
},
{
type: "구기",
title: "탁구 교실",
price: "60,000원",
facility: "탁구장",
time: "평일 18:00-19:30",
icon: "sports"
},
{
type: "구기",
title: "테니스 중급반",
price: "90,000원",
facility: "테니스장",
time: "화목토 20:00-21:30",
icon: "sports_tennis"
}
],
waterSports: [
{
type: "수상",
title: "자유형 마스터",
price: "90,000원",
facility: "수영장",
time: "평일 07:00-08:00",
icon: "pool"
},
{
type: "수상",
title: "아쿠아로빅",
price: "70,000원",
facility: "수영장",
time: "월수금 10:00-11:00",
icon: "pool"
},
{
type: "수상",
title: "수영 초급반",
price: "85,000원",
facility: "수영장",
time: "화목 19:00-20:00",
icon: "pool"
},
{
type: "수상",
title: "접영 특강",
price: "100,000원",
facility: "수영장",
time: "토일 08:00-09:30",
icon: "pool"
},
{
type: "수상",
title: "아쿠아 다이빙",
price: "120,000원",
facility: "다이빙풀",
time: "토일 10:30-12:00",
icon: "scuba_diving"
}
],
fitness: [
{
type: "피트니스",
title: "저녁 요가",
price: "100,000원",
facility: "요가스튜디오",
time: "평일 19:00-20:00",
icon: "self_improvement"
},
{
type: "피트니스",
title: "코어 필라테스",
price: "120,000원",
facility: "필라테스센터",
time: "평일 18:00-19:00",
icon: "fitness_center"
},
{
type: "피트니스",
title: "스피닝 클래스",
price: "110,000원",
facility: "피트니스센터",
time: "화목 20:00-21:00",
icon: "directions_bike"
},
{
type: "피트니스",
title: "점심 필라테스",
price: "130,000원",
facility: "필라테스센터",
time: "평일 12:00-13:00",
icon: "fitness_center"
},
{
type: "피트니스",
title: "아침 스트레칭",
price: "80,000원",
facility: "피트니스센터",
time: "평일 06:00-07:00",
icon: "accessibility_new"
},
{
type: "피트니스",
title: "크로스핏",
price: "150,000원",
facility: "크로스핏박스",
time: "월수금 19:30-21:00",
icon: "fitness_center"
}
]
};
// 프로그램 카드 렌더링 함수
function renderProgramCards(programs, containerId) {
const container = document.querySelector(containerId);
if (!container) return;
const cardsHTML = programs.map(program => `
<div class="program-card">
<span class="program-type type-${program.type.toLowerCase()}">${program.type}</span>
<h4>${program.title}</h4>
<p class="price">${program.price}</p>
<p class="facility-info">${program.facility}</p>
<p class="facility-info">${program.time}</p>
<span class="material-icons">${program.icon}</span>
</div>
`).join('');
container.innerHTML = cardsHTML;
}
// DOM 로드 시 실행
document.addEventListener('DOMContentLoaded', function () {
// 연령별 프로그램 렌더링
renderProgramCards(programData.youth, '.program-section:nth-child(3) .program-slider');
renderProgramCards(programData.adult, '.program-section:nth-child(3) .program-slider:nth-child(5)');
renderProgramCards(programData.senior, '.program-section:nth-child(3) .program-slider:nth-child(7)');
// 종목별 프로그램 렌더링
renderProgramCards(programData.ballSports, '.program-section:nth-child(4) .program-slider:nth-child(3)');
renderProgramCards(programData.waterSports, '.program-section:nth-child(4) .program-slider:nth-child(5)');
renderProgramCards(programData.fitness, '.program-section:nth-child(4) .program-slider:nth-child(7)');
// 슬라이더 기능 구현
document.querySelectorAll('.program-slider').forEach(slider => {
let isDown = false;
let startX;
let scrollLeft;
slider.addEventListener('mousedown', (e) => {
isDown = true;
startX = e.pageX - slider.offsetLeft;
scrollLeft = slider.scrollLeft;
});
slider.addEventListener('mouseleave', () => {
isDown = false;
});
slider.addEventListener('mouseup', () => {
isDown = false;
});
slider.addEventListener('mousemove', (e) => {
if (!isDown) return;
e.preventDefault();
const x = e.pageX - slider.offsetLeft;
const walk = (x - startX) * 2;
slider.scrollLeft = scrollLeft - walk;
});
});
});
CSS
/* 전역 스타일 */
:root {
--primary-color: #2D63E2;
--border-color: #E1E2E3;
}
body {
margin: 0;
font-family: -apple-system, sans-serif;
}
/* 헤더 스타일 */
.header {
padding: 1rem;
border-bottom: 1px solid var(--border-color);
}
.search-bar {
display: flex;
align-items: center;
max-width: 1200px;
margin: 0 auto;
}
.logo {
color: var(--primary-color);
font-weight: bold;
margin-right: 2rem;
}
.search-input {
flex: 1;
padding: 0.5rem;
border: 2px solid var(--primary-color);
border-radius: 4px;
}
/* 지도 섹션 */
.map-section {
height: 200px;
background: #f5f5f5;
margin-bottom: 2rem;
}
/* 프로그램 섹션 */
.program-section {
max-width: 1200px;
margin: 0 auto;
padding: 1rem;
}
.section-title {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 1rem;
}
/* 프로그램 카드 슬라이더 */
.program-slider {
display: flex;
overflow-x: auto;
gap: 1rem;
padding: 1rem 0;
}
.program-card {
min-width: 200px;
padding: 1rem;
border: 1px solid var(--border-color);
border-radius: 8px;
background: white;
}
.program-type {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.875rem;
margin-bottom: 0.5rem;
}
.type-youth {
background: #4CAF50;
color: white;
}
.type-adult {
background: #2196F3;
color: white;
}
.type-senior {
background: #9C27B0;
color: white;
}
.type-ball {
background: #FF9800;
color: white;
}
.type-water {
background: #00BCD4;
color: white;
}
.type-fitness {
background: #E91E63;
color: white;
}
.price {
color: #FF4B4B;
font-weight: bold;
}
.facility-info {
font-size: 0.875rem;
color: #666;
}
시연영상
'HTML/CSS' 카테고리의 다른 글
[HTML/CSS/JS] 웹페이지 스탑워치, 타이머 만들기 (0) | 2024.12.07 |
---|---|
[HTML/CSS] 반응형 웹페이지 만들기 (2) | 2024.12.01 |
[HTML/CSS] CSS transition 속성 transition-timing-function (2) | 2024.10.22 |
[HTML/CSS]CSS Flexbox에 대한 가이드 (0) | 2024.07.10 |
[HTML/CSS] Position 속성에 대한 가이드 (0) | 2024.07.01 |