IT/development

[Node.js] 무한 스크롤 적용 소스 (feat. mobile)

어흥꼬비 2025. 1. 11.
반응형

목차

    개발환경

    백엔드 프론트엔드
    Express.js(Node.js 기반 웹 프레임워크) EJS 템플릿 엔진(Embedded JavaScript)

    라우트

    router.get('/searchHistoryScroll', MyController.getHistoryIndexUsePaging);

    라우트 파일에서 /searchHistoryScroll url을 MyController의 getHistoryIndexUsePaging 함수와 매핑

    컨트롤러

    // 상단 정의 부분 
    const axios = require('axios');
    
    // 라우트에 정의된 함수
    exports.getHistoryIndexUsePaging  = async(req, res) => {
      let pageNo = req.query.page[0];            // 조회할 페이지 번호   
    
      queryParams = {
        page: pageNo,
        size: 10, 								// 한번에 조회할 데이터 개수
      };  
    
      const headers = {
        'Content-Type': 'application/json',
        //세션 아이디
        'hiddenValue': req.session.MEMB.MEMB_ID
      };  
    
      try {
        // 데이터 조회 API 호출
        const historyUrl = `${process.env.TEST_API}/history`;
        // API 조회 결과
        const historyList = await axios.get(historyUrl, {
          headers: headers, 
          params: queryParams,
          timeout: 20000
        });   
        //데이터를 json형식으로 클라이언트에 반환
        res.json({    
          data: { 
            historyList: historyList.data
          } 
        }); 
    
      } catch (error) {
        console.error("error :: " + error);
        res.status(500).json({ success: false, message: 'Failed to fetch data' });
      }
    }

    서버에서는 클라이언트에서 전달된 페이지 번호와 데이터 사이즈를 API의 parameter의 인자로 넘기며

    API를 통해 데이터를 받아와서 정상인 경우 json형식의 데이터를, 오류 발생 시 로그를 출력하고

    상태코드 500과 실패 메시지를 반환한다.

    EJS

    <header class="sub-header nav-padding">
      <nav class="loginfixed">
        <dl>
          <dd></dd>
        </dl>
        <dl>
          <dd>내역</dd>
        </dl>
        <dl>  
          <!-- 화면 로드 시 전체 개수를 가지고 있음(이건 서버의 다른 함수에서 전달했다고 가정) -->
          <input type="hidden" id="totalCountHidden" value="<%- data.totalCount %>">
        </dl>
      </nav>
    </header>
    
    <div class="list-box">
      <div class="list-item-box">
        <div class="list-item">
          <div class="list-top">
            <dl>
              <!-- 화면 로드 시 전달했다고 가정 -->	
              <dd>총 <%- data.totalCount %>건</dd>
            </dl>   
          </div>
          <!-- 이 요소 밑에 동적으로 데이터를 추가 -->
          <div class="list-bottom"></div>
        </div>
      </div>
    </div>
    
    <!-- API 호출하기 위해 axios 불러옴 -->
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script>
      let isLoading = false; 			// 데이터 로드 여부		
      let page = 1;  					// 현재 페이지 번호			
      let lastScrollY = window.scrollY; // 마지막 스크롤 위치
    
      $(document).ready(function () { 
        loadMorePosts(page);  
      }); 
    
      // 스크롤 이벤트
      window.addEventListener('scroll', function () {
        if (window.scrollY > lastScrollY) { 	// 현재 스크롤 위치가 이전 스크롤 위치보다 큰 경우(아래를 향함)
          // 화면의 최하단에 도달했는지 판단
          if (window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 1) {
            if (!isLoading) {	// 데이터가 로드중이 아닐 때 
              isLoading = true;
              page++;				 
              loadMorePosts(page); // 추가 데이터를 불러오는 함수
            } 
          }
        } 
        lastScrollY = window.scrollY; // 스크롤 위치 업데이트
      });
    
      //데이터 조회 후 데이터가 있으면 DOM의 마지막 요소에 추가
      async function loadMorePosts(pageNo) { 
          var totalCount = $("#totalCountHidden").val();
    
    		  isLoading = false;
    
          let htmlContent = ''; 
    
    			const queryParams = {
    				page: Array.isArray(pageNo) ? pageNo[0] : pageNo
    			};  
    
    			const requestUrl = `/searchHistoryScroll?${new URLSearchParams(queryParams).toString()}`;
    
    			try {
    				const response = await axios.get(requestUrl, {
    					params: queryParams,
    					timeout: 20000,
    				});
    
    				const HistoryList = response.data;
    
            if(totalCount < 1) {
              htmlContent += `    
              <div class="no-list">
                <ul>
                  <li>내역이 없어요!</li>
                </ul>
              </div>    
            `;  
    
              $('.list-bottom').append(htmlContent);
            }  else {      
    					updateCreditHistory(historyList.data.historyList);
            }
    
    			} catch (error) {
    				console.error('데이터를 불러오는 중 오류 발생:', error);
    			} 
    		}   
    
        // 조회한 데이터를 DOM 요소의 마지막 자식으로 append
        function updateCreditHistory(data) {   
          let container = $('.list-bottom');
          let htmlContent = '';
          
          for (let i = 0; i < data.data.length; i++) {  
            const item = data.data[i];
            htmlContent += `  
              <dl>
                <dd>${item.startDate.substring(0, 16).replace(/-/g, '.')}</dd>
                <dd>${item.endDate.substring(0, 16).replace(/-/g, '.')}</dd>
              </dl>
            `;      
          } 
          $('.list-bottom').append(htmlContent);
        }
    </script>

    전체 개수를 가져와서 데이터가 없으면 내역이 없다고 표시하고 데이터가 있으면

    스크롤 이벤트 시 페이지 번호 증가시키며 서버를 호출 해 데이터가 있으면 .list-bottom요소의 마지막에

    계속 append를 한다.(덮어쓰는게 아니다.)

    기존의 웹 방식에서는 페이지를 누를 때 마다 한 페이지에 보여질 사이즈만큼 계속 새로 생성했는데 모바일에서는 스크롤 할 때마다 새로 가져온 데이터가 하단에 추가가 되어야 하고 다시 위로 스크롤 했을 때에도 기존 데이터도 유지 되어야 한다.


    개인 스터디 기록을 메모하는 공간이라 틀린점이 있을 수 있습니다.

    틀린 점 있을 경우 댓글 부탁드립니다.

    반응형

    댓글