쇼핑몰등에서 마우스 그래그를 하다보면 상품이 계속 생겨나는 것을 볼 수 있다.
이를 무한스크롤이라 하는데 일종의 페이징이다.
- 아래 소스코드를 JSFIDDLE 에서 돌려보자 HTML 영역에 모두 집어넣고 RUN 시키면 동작한다.
심지어 가짜JSON API까지 사용하여 상품이 있는 것 처럼 테스트 가능하다
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>jQuery 무한스크롤</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<style>
/* 기본 레이아웃 및 앱 컨테이너 스타일 */
body {
background: #f2f2f7;
display: flex;
justify-content: center;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
.app-container {
width: 875px;
max-width: 100%;
background: #fff;
border: 1px solid #ddd;
border-radius: 16px;
overflow: hidden;
display: flex;
flex-direction: column;
}
#scroll-area {
height: 600px;
overflow-y: auto;
}
/* 헤더 스타일 */
.header {
padding: 1rem;
background-color: #f8f9fa;
border-bottom: 1px solid #eee;
}
.header-title {
margin: 0;
font-size: 1.25rem;
}
.page-info {
color: #6c757d;
font-weight: 400;
font-size: 0.9rem;
}
/* 상품 목록 그리드 (Flexbox) */
#product-list {
display: flex;
flex-wrap: wrap;
padding: 10px; /* 아이템 사이의 외부 간격을 위해 */
}
.product-item {
padding: 5px; /* 아이템 사이의 내부 간격을 위해 */
box-sizing: border-box;
}
/* 상품 카드 디자인 */
.product-card {
border: 1px solid #eee;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.07);
background-color: #fff;
height: 100%;
display: flex;
flex-direction: column;
transition: all 0.4s ease;
opacity: 0;
transform: translateY(20px);
}
.product-card.show {
opacity: 1;
transform: translateY(0);
}
.product-card-img {
padding: 1rem;
height: 180px;
object-fit: contain;
}
.product-card-body {
padding: 1rem;
display: flex;
flex-direction: column;
flex-grow: 1;
}
.product-card-title {
margin: 0;
font-size: 1rem;
font-weight: 600;
}
.product-card-price {
color: #0d6efd;
font-weight: bold;
margin: 0.5rem 0;
}
.product-card-button {
margin-top: auto;
width: 100%;
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
border-radius: 0.25rem;
border: 1px solid #0d6efd;
background-color: transparent;
color: #0d6efd;
cursor: pointer;
transition: all 0.2s;
}
.product-card-button:hover {
background-color: #0d6efd;
color: #fff;
}
/* 로딩 인디케이터 */
.loading {
text-align: center;
padding: 1rem;
color: #6c757d;
}
</style>
</head>
<body>
<div class="app-container">
<div class="header">
<h5 class="header-title">
상품 목록
<small class="page-info" id="page-info">(0/0)</small>
</h5>
</div>
<div id="scroll-area">
<div id="product-list"></div>
<div class="loading" id="loading" style="display:none;">
Loading...
</div>
</div>
</div>
<script>
// 환경 설정
const CONFIG = {
// 데이터를 요청할 서버의 기본 API 주소
API_BASE_URL: "https://dummyjson.com/products",
// 한 번의 API 호출로 가져올 아이템(상품)의 개수
PAGE_SIZE: 6,
// 한 줄에 표시할 아이템(상품)의 개수 (레이아웃 용도)
ITEMS_PER_ROW: 1
};
// 현재 로드해야 할 페이지 번호. 스크롤 할 때마다 1씩 증가합니다.
let currentPage = 1;
// 데이터 로딩 중인지 여부를 판별하는 변수. (중복 API 호출 방지용)
let isLoading = false;
// 서버에 더 불러올 데이터가 남아있는지 판별하는 변수.
let hasMore = true;
// 서버에 있는 전체 상품의 총개수. (UI 표시용: '12/100개')
let totalProducts = 0;
/**
* 서버에 다음 페이지의 상품 데이터를 요청하고 화면에 표시
*/
function loadMoreProducts() {
if (isLoading || !hasMore) return;
isLoading = true;
$("#loading").show();
const skip = (currentPage - 1) * CONFIG.PAGE_SIZE;
const requestUrl = `${CONFIG.API_BASE_URL}?limit=${CONFIG.PAGE_SIZE}&skip=${skip}`;
$.ajax({
url: requestUrl,
method: "GET",
success: function(data) {
if (totalProducts === 0) {
totalProducts = data.total;
}
const products = data.products;
if (!products || products.length === 0) {
hasMore = false;
$("#loading").show().text("더 이상 상품이 없습니다.");
}
renderProducts(products);
const currentProductCount = $("#product-list").children().length;
$("#page-info").text(`(${currentProductCount}/${totalProducts})`);
currentPage++;
},
error: function() {
alert("상품을 불러오는 중 오류가 발생했습니다.");
hasMore = false;
},
complete: function() {
isLoading = false;
if (hasMore) {
$("#loading").hide();
}
}
});
}
/**
* 상품 데이터 배열을 받아 HTML 카드를 만들어 화면에 추가
*/
function renderProducts(products) {
if (!products) return;
const flexBasis = 100 / CONFIG.ITEMS_PER_ROW;
$.each(products, function(i, product) {
const $card = $(`
<div class="product-item" style="flex-basis: ${flexBasis}%;">
<div class="product-card">
<img src="${product.thumbnail}" class="product-card-img" alt="${product.title}">
<div class="product-card-body">
<h6 class="product-card-title">${product.title}</h6>
<p class="product-card-price">$${product.price}</p>
<button class="product-card-button">장바구니</button>
</div>
</div>
</div>
`);
$("#product-list").append($card);
setTimeout(() => $card.find(".product-card").addClass("show"), 100);
});
}
// 무한 스크롤 이벤트
$("#scroll-area").on("scroll", function() {
const scrollArea = $(this);
if (scrollArea.scrollTop() + scrollArea.innerHeight() >= scrollArea[0].scrollHeight - 30) {
loadMoreProducts();
}
});
// 문서가 준비되면 첫 번째 상품 목록을 로드
$(document).ready(function() {
loadMoreProducts();
});
</script>
</body>
</html>
우선 위 코드는 이해하는것 보다 복사해서 쓰려고 만들어 둔 것이다.
1., HTML 내 이 부분을 자신의 영역에 복사한다
<div id="scroll-area">
<div id="product-list"></div>
<div class="loading" id="loading" style="display:none;">
Loading...
</div>
</div>
2. 그, 다음 스크립트 코드를 모두 복사한다.
id가 scroll-area 는 무한스크롤 이벤트를 주는 곳이고
id가 product-list 는 동적 html로 만들어진 상품을 넣기위한 요소이다.
3. 자바스크립트 중
// 환경 설정
const CONFIG = {
// 데이터를 요청할 서버의 기본 API 주소
API_BASE_URL: "https://dummyjson.com/products",
// 한 번의 API 호출로 가져올 아이템(상품)의 개수
PAGE_SIZE: 6,
// 한 줄에 표시할 아이템(상품)의 개수 (레이아웃 용도)
ITEMS_PER_ROW: 1
};
ITEM_PER_ROW 값을 수정하게 되면
상품의 가로로 보여지는 수가 달라진다.
값이 1일 때
값이 3일 때
이 후 AJAX 코드는 자신의 서버사이드에 맞게 고쳐서 사용하자
'Web > JS & jQuery' 카테고리의 다른 글
글자 Byte 계산 (SMS발송 등...) (3) | 2025.08.18 |
---|---|
동적 form 전송 (2) | 2025.07.28 |
JS와 jQuery 를 사용한 모듈화 (0) | 2024.09.26 |
유효성 검사 (1) | 2024.09.03 |
jQuery-Confirm 플러그인 (1) | 2024.09.01 |