jQuery + Ajax 무한 스크롤 (페이징)

2025. 9. 3. 21:13·Web/JS & jQuery




쇼핑몰등에서 마우스 그래그를 하다보면 상품이 계속 생겨나는 것을 볼 수 있다.
이를 무한스크롤이라 하는데 일종의 페이징이다.

- 아래 소스코드를 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
'Web/JS & jQuery' 카테고리의 다른 글
  • 글자 Byte 계산 (SMS발송 등...)
  • 동적 form 전송
  • JS와 jQuery 를 사용한 모듈화
  • 유효성 검사
iyak
iyak
자료 정리
  • iyak
    iyak
    나의 정리 공간

    post | manage
  • 전체
    오늘
    어제
    • 분류 전체보기 (55)
      • C#.NET (30)
        • C# (9)
        • ASP.NET (19)
        • WinForm (0)
        • 설정 (2)
      • JAVA,Spring (0)
        • Spring (0)
      • DB (9)
        • SQLServer (9)
        • MySQL & MaridDB (0)
      • Web (12)
        • JS & jQuery (10)
        • Web개발 관련사이트 (2)
      • 기타 (4)
        • 프로그램 (4)
  • 블로그 메뉴

    • 홈
    • 태그
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
iyak
jQuery + Ajax 무한 스크롤 (페이징)
상단으로

티스토리툴바