Chapter 9-3 Django Post 목록 무한 스크롤 만들기

2025. 5. 14. 15:13·Django

🔁 Django + Waypoints.js로 무한 스크롤 구현하기

Django에서 포스트 목록을 페이지네이션 하면서, 사용자가 스크롤을 내릴 때 자동으로 다음 페이지 데이터를 불러오는 "무한 스크롤" 기능을 구현하려면 Waypoints.js와 infinite-scroll 플러그인이 매우 유용합니다.

이 글에서는 Swiper.js로 이미지 슬라이더까지 결합하여, Django 기반의 소셜 피드 UI를 자연스럽고 효율적으로 구성하는 방법을 정리합니다.


1️⃣ Waypoints.js란?

  • 📌 스크롤 위치를 감지하여 특정 이벤트를 실행할 수 있도록 도와주는 JS 라이브러리입니다.
  • 📦 Infinite Scroll 플러그인을 함께 쓰면, 특정 요소가 뷰포트에 도달했을 때 자동으로 다음 페이지 요청 가능
  • 📦 다운로드후 static 폴더에 waypoints 폴더 만든후 jquery.waypoints.min.js와 infinite.min.js 넣어주기

1️⃣ jQuery

  • ✅ jQuery는 JavaScript를 더 쉽게 쓰도록 도와주는 라이브러리입니다.
  • 🔧 HTML 요소 선택, 이벤트 처리, AJAX 통신 등을 간결하게 작성할 수 있어요.
  • ⚠️ 최신 웹에서는 Vanilla JS나 React 등으로 대체되는 경우가 많지만, 여전히 레거시 코드에서 자주 사용됩니다
  • ⚠️ 다운로드후 static 폴더에 js 폴더를 만든후 

2️⃣ 전체  코드 흐름 설명

post/view.py

User = get_user_model()

class PostListView(ListView):
    model = Post
    queryset = Post.objects.all().select_related('user').prefetch_related('images')
    template_name = 'post/list.html'              # 전체 레이아웃용 템플릿
    context_object_name = 'object_list'
    paginate_by = 5
    ordering = ('-created_at', )

    def get(self, request, *args, **kwargs):
        """
        - 일반 요청: 전체 페이지(list.html) 렌더링
        - AJAX 요청: 내용 부분(post_list_content.html)만 렌더링
        """
        self.object_list = self.get_queryset()
        context = self.get_context_data()

        if request.headers.get('x-requested-with') == 'XMLHttpRequest':
            # 부분 템플릿만 반환
            html = render_to_string('post/post_list_content.html', context, request=request)
            return HttpResponse(html)
        return super().get(request, *args, **kwargs)

 

post/list.html

{# templates/post/list.html #}
{% extends 'base.html' %}
{% load static %}

{% block style %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper/swiper-bundle.min.css"/>
<style>
  .post-image { aspect-ratio:1/1; object-fit:cover; }
</style>
{% endblock %}

{% block content %}
<div class="row">
  <div class="col-10 offset-1 col-lg-6 offset-lg-3 infinite-container">
    {% include 'post/post_list_content.html' %}
  </div>
</div>
{% endblock %}

{% block js %}
<script src="https://cdn.jsdelivr.net/npm/swiper/swiper-bundle.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
  let loading = false;

  // 1) Swiper 초기화
  function initSwipers() {
    document.querySelectorAll('.swiper').forEach(el => {
      new Swiper(el, {
        direction: 'horizontal',
        loop: false,
        pagination: { el: el.querySelector('.swiper-pagination') },
      });
    });
  }
  initSwipers();

  // 2) 다음 페이지 AJAX 로드 함수
  async function loadNextPage() {
    if (loading) return;
    const link = document.querySelector('.infinite-more-link');
    if (!link) return;            // 다음 페이지 없으면 중단
    loading = true;

    try {
      const res = await fetch(link.href, {
        headers: { 'X-Requested-With': 'XMLHttpRequest' }
      });
      const html = await res.text();
      // 임시 컨테이너에 받기
      const tmp = document.createElement('div');
      tmp.innerHTML = html;

      // 새 아이템 붙이기
      tmp.querySelectorAll('.infinite-item').forEach(item =>
        document.querySelector('.infinite-container').appendChild(item)
      );
      // 기존 링크·sentinel 제거
      document.querySelector('.infinite-more-link').remove();
      document.getElementById('scroll-sentinel').remove();

      // 다음 링크·sentinel 다시 붙이기 (없으면 무한스크롤 종료)
      const newLink = tmp.querySelector('.infinite-more-link');
      if (newLink) {
        document.querySelector('.infinite-container').appendChild(newLink);
        const newSentinel = document.createElement('div');
        newSentinel.id = 'scroll-sentinel';
        document.querySelector('.infinite-container').appendChild(newSentinel);
        observer.observe(newSentinel);
      }
      // Swiper 재초기화
      initSwipers();
    } catch (err) {
      console.error('페이지 로드 실패:', err);
    } finally {
      loading = false;
    }
  }

  // 3) IntersectionObserver 설정
  const observer = new IntersectionObserver(entries => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        loadNextPage();
      }
    });
  }, {
    root: null,
    rootMargin: '0px 0px 200px 0px',
    threshold: 0
  });

  // 초기 sentinel 관찰 시작
  const sentinel = document.getElementById('scroll-sentinel');
  if (sentinel) observer.observe(sentinel);
});
</script>
{% endblock %}

post_list_content.html

{# templates/post/post_list_content.html #}
{% load static %}

{% for post in object_list %}
  <div class="border-bottom my-4 infinite-item">
    <div class="mb-2">
      <span class="p-2 border rounded-circle me-2">
        <i class="fa-solid fa-user" style="width: 16px; padding-left: 1px;"></i>
      </span>
      {{ post.user.nickname }}
    </div>
    <div class="swiper" style="max-height: 500px;">
      <div class="border-1 swiper-wrapper">
        {% for post_image in post.images.all %}
          <div class="swiper-slide">
            <img class="img-fluid post-image" src="{{ post_image.image.url }}" alt="">
          </div>
        {% endfor %}
      </div>
      <div class="swiper-pagination"></div>
    </div>
    <div class="my-2">
      {{ post.content|linebreaksbr }} {{ post.id }}
    </div>
  </div>
{% endfor %}

{% if page_obj.has_next %}
  <a href="?page={{ page_obj.next_page_number }}" class="infinite-more-link"></a>
  <div id="scroll-sentinel" style="height:1px;"></div>
{% endif %}

 


✅ 기대 효과

  • 포스트 리스트를 스크롤할 때마다 자연스럽게 다음 포스트 불러오기
  • 포스트마다 Swiper로 이미지 슬라이드 제공
  • UI/UX가 부드럽고, 사용자 체감 속도 향상

📎 참고

  • Swiper.js: https://swiperjs.com/
  • Waypoints: http://imakewebthings.com/waypoints/
  • Infinite Scroll 플러그인: https://github.com/Infinite-AJAX-Scroll/waypoints-infinite
  • jQuery: https://jquery.com/

 

 

 

이와 같이 Django + Waypoints + Swiper.js를 결합하면, 블로그나 인스타그램 피드처럼 자연스러운 무한 스크롤 + 이미지 갤러리 구현이 가능합니다! 💡

'Django' 카테고리의 다른 글

Chapter 9-5 Django 댓글 모델 만들기, 태그 모델 만들기  (0) 2025.05.14
Chapter 9-4 Django 포스트 생성, 수정 페이지 만들기  (0) 2025.05.14
Chapter 9-2 Django Post 목록 페이지 만들기  (0) 2025.05.14
Chapter 9-1 Django Post 기능 구현하기  (0) 2025.05.14
🔍 Django에서 User.objects.model의 정체는?  (0) 2025.05.12
'Django' 카테고리의 다른 글
  • Chapter 9-5 Django 댓글 모델 만들기, 태그 모델 만들기
  • Chapter 9-4 Django 포스트 생성, 수정 페이지 만들기
  • Chapter 9-2 Django Post 목록 페이지 만들기
  • Chapter 9-1 Django Post 기능 구현하기
Chansman
Chansman
안녕하세요! 코딩을 시작한 지 얼마 되지 않은 초보 개발자 찬스맨입니다. 이 블로그는 제 학습 기록을 남기고, 다양한 코딩 실습을 통해 성장하는 과정을 공유하려고 합니다. 초보자의 눈높이에 맞춘 실습과 팁, 그리고 개발하면서 겪은 어려움과 해결 과정을 솔직하게 풀어내려 합니다. 함께 성장하는 개발자 커뮤니티가 되기를 바랍니다.
  • Chansman
    찬스맨의 프로그래밍 스토리
    Chansman
  • 전체
    오늘
    어제
    • 분류 전체보기 (787) N
      • Python (32)
      • 프로젝트 (110) N
      • 과제 (25)
      • Database (40)
      • 멘토링 (11)
      • 특강 (37)
      • 기술블로그 (41)
      • 기술블로그-Fastapi편 (33)
      • 기술블로그-Django편 (153)
      • 기술블로그-Flask편 (36)
      • AI 분석 (5)
      • HTML & CSS (31)
      • JavaScript (17)
      • AWS_Cloud (21)
      • 웹스크래핑과 데이터 수집 (14)
      • Flask (42)
      • Django (77)
      • Fastapi (16)
      • 연예 (14)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    RM
    travel ban
    btscomeback
    homebartrend
    chinanightlife
    college reunions
    titaniumcase
    btsjungkook
    basalcellcarcinoma
    americaparty
    global politics
    뷔
    lawsuitculture
    remittance
    smartphonedurability
    gpterror
    self-growth
    btsreunion
    urbantrends
    trumpmuskclash
    americanlaw
    newpoliticalparty
    bts
    hotcoffeecase
    classaction
    btsdischarge
    life reflection
    chatgpterror
    livebroadcast
    youngprofessionals
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Chansman
Chapter 9-3 Django Post 목록 무한 스크롤 만들기
상단으로

티스토리툴바