본문 바로가기
HTML/CSS

[HTML/CSS/JS] 프로그램 카드 가로 스크롤 슬라이더 만들기

by teamnova 2024. 11. 24.
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;
}

 

시연영상