JavaScript

[나의 toy 프로젝트] - [infiniteScroll 기법] 무한스크롤 구현하기

건강한_개발자 2023. 6. 11. 19:34

## infinite scroll

- 무한스크롤 구현 - 컨텐츠의 끝이 화면 끝에 닿으면 api가 호출되게 구현

- todolist 와 달리 외부 api 연동

- fetch, async, await

 

## API 연동

https://jsonplaceholder.typicode.com/ - 프로토타이핑용 api

- /posts 활용

- [json-server](https://www.npmjs.com/package/json-server) 만든 곳에서 만들었기 때문에 사용 방법은 json-server와 동일

 


 

1. 기본 html 구성

 

API 연동

https://jsonplaceholder.typicode.com/

  • 이 사이트의 api는 test나 prototyping을 위한 API이다.
  • 사이트에 들어가서 Resources의 posts를 활용했다.
    • json-server 만든 곳에서 만들었기 때문에 사용 방법은 json-server와 동일 ( todolist에서 사용했음)

이번 무한스크롤 구현시, 페이지 네이션을 활용할 것.

 



 

1. 데이터 가져오기 

;(function () {
  'use strict'

  const get = (target) => {
    return document.querySelector(target)
  }

  const getPost = async () => {
    const API_URL = 'https://jsonplaceholder.typicode.com/posts'
    const response = await fetch(API_URL)
    if (!response.ok) {
      //ok가 아닌경우
      throw new Error('에러발생')
    }
    //ok인 경우
    return await response.json()
  }

  const loadPost = async () => {
    const response = await getPost()
    console.log(response)
  }

  window.addEventListener('DOMContentLoaded', () => {
    loadPost()
  })
})()

<코드설명>

 

code 1

window.addEventListener('DOMContentLoaded', () => {
    loadPost()
  })

기본적으로 DOMContentLoaded 를 이용하여, DOM이 다 불러와 졌을때, loadPost()가 동작하도록 작성한다.

 

 

code2

 

API URL 가져오기

https://jsonplaceholder.typicode.com/

 

JSONPlaceholder - Free Fake REST API

{JSON} Placeholder Free fake API for testing and prototyping. Powered by JSON Server + LowDB. Tested with XV. Serving ~2 billion requests each month.

jsonplaceholder.typicode.com

  1. 위 사이트이 들어간다.
  2. 스크롤을 내려 posts 클릭

  • 이 url을 복사해서 사용하면 된다.
  • 총 100개의 데이터가 있다.

 

const getPost = async () => {
    const API_URL = 'https://jsonplaceholder.typicode.com/posts'
    const response = await fetch(API_URL)
    if (!response.ok) {
      //ok이가 아닌경우
      throw new Error('에러발생')
    }
    //ok이인 경우
    return await response.json()
  }
  • async , await을 이용해 API_URL을 가져온다.
    • await으로 받으면 const response = await fetch(API_URL) 와 같이 변수에 담을 수 있다.
  • response가 ok가 아니면, throw new Error('에러발생') 로 에러를 발생시킨다.
  • response가 ok인 경우, return await response.json() → resopnse를 return 해준다.

 

code3 → getPost에서 return한 response 받아오기

const loadPost = async () => {
    const response = await getPost()
    console.log(response)
  }

console로 response를 출력해보면 다음과 같이 100개의 데이터가 잘 출력되는 것이 확인된다.

 

 

code4 → code3에서 받아온 response(100개데이터)를 하나하나 보여주는 작업하기

 

<div class posts></div> → 이곳에 post들을 넣어줄 것이다.

 

const loadPost = async () => {
    const response = await getPost()
    showPosts(response)
  }
  1. getPost() 에서 받아온 데이터를 response 변수에 담아준다.
  2. showPosts()함수를 만들어 넘겨준다.
    • showPosts()에서 데이터들을 하나하나 처리해 dom을 구성할 것이다.

 

showPosts() 함수 작성하기

const get = (target) => {
    return document.querySelector(target)  //dom element를 가져오는 get함수 
  }
const $posts = get('.posts')


const showPosts = (posts) => {
    posts.forEach((post) => {
      const $post = document.createElement('div')
      $post.classList.add('post')
      $post.innerHTML = `
        <div class="header>
          <div class="id">${post.id}</div>
          <div class="title">${post.title}</div>
        </div>
        <div class="body">
          ${post.body}
        </div>
      `
      $posts.appendChild($post)
    })
  }

1. posts데이터를 하나하나 처리하기 위해 forEach()를 사용한다.

 

// <div class posts></div> → 이곳에 post들을 넣어줄 것이다.

  1. post class를 가진 div를 만들어 준다.
  2. $posts에 appendChild로 $post를 붙여준다 .
  3. 확인해보면 100개의 div class=”post” 가 잘 들어간 것이 확인된다.

 

 

 


 

2. 페이지 끝 체크해서 데이터 추가로 가져오기

 

현재는 100개 데이터를 한번에 가져오기 때문에 제한을 걸어줄 것이다.

 

const limit = 10

const getPost = async () => {
    const API_URL = `https://jsonplaceholder.typicode.com/posts?_limit=${limit}`
    const response = await fetch(API_URL)
    if (!response.ok) {
      //ok이가 아닌경우
      throw new Error('에러발생')
    }
    //ok이인 경우
    return await response.json()
  }
  1. limit 변수를 만들고 10을 할당한다 → 한번에 가져올 데이터 숫자
  2. getPost()의 API_URL로 다음과같이 limit을 적용해준다.
const API_URL = `https://jsonplaceholder.typicode.com/posts?_limit=${limit}`

10개만 화면에 보이는것이 확인된다 .

 

 

스크롤 끝 감지하기

window.addEventListener('DOMContentLoaded', () => {
    loadPost()

    window.addEventListener('scroll', onScroll)
  })

scroll이벤트 발생시 onScroll 함수가 작동하도록 한다.

 

 

 

onScroll함수 작성하기

let page = 1

  const getPost = async () => {
    const API_URL = `https://jsonplaceholder.typicode.com/posts?_page=${page}&_limit=${limit}`
    const response = await fetch(API_URL)
    if (!response.ok) {
      //ok이가 아닌경우
      throw new Error('에러발생')
    }
    //ok이인 경우
    return await response.json()
  }


const onScroll = () => {
    const { scrollTop, scrollHeight, clientHeight } = document.documentElement

    if (scrollTop + clientHeight >= scrollHeight - 5) {
      loadPost()
    }
  }

1. onScroll함수는 스크롤발생시 다음 값들을 받아온다.

const { scrollTop, scrollHeight, clientHeight } = document.documentElement

2. scrollTop + clientHeight >= scrollHeight - 5 일떄, loadPost()를 발생시킨다.

 if (scrollTop + clientHeight >= scrollHeight - 5) {
      loadPost()
    }

3. page 변수를 만들어준다.

let page = 1

4. getPost 함수의 API_URL을 수정해준다. 

const API_URL = `https://jsonplaceholder.typicode.com/posts?_page=${page}&_limit=${limit}`

 

네트워크텝으로 확인해보면 다음과같다.

++ page 를 2로 바꾸면 다음과같이 출력된다.

 

이제 할것은, 스크롤 끝이 닿을때마다, page를 늘려주는 것이다.

const onScroll = () => {
    const { scrollTop, scrollHeight, clientHeight } = document.documentElement

    if (scrollTop + clientHeight >= scrollHeight - 5) {
      page++
      loadPost()
    }
  }

위와같이 scrollTop + clientHeight >= scrollHeight - 5 일때마다 page++을 해주면 된다.

 


 

잘 동작하는 것처럼 보이지만, 문제점이 하나 생겼다.

 

문제점은, 페이지끝에 닿을떄마다, 이미 데이터를 다 가져왔는데도, 계속 불러온다는 것이다.

 

 

해결법  ==>  제한을 걸어준다.

	const end = 100 //데이터의 총 갯수
  let total = 10 // 여태까지 불러온 데이터의 개수를 나타낸다.

end , total 변수를 만들어준다.

const onScroll = () => {
    const { scrollTop, scrollHeight, clientHeight } = document.documentElement

    if (total === end) {
      return
    }
    if (scrollTop + clientHeight >= scrollHeight - 5) {
      page++
      total += 10
      loadPost()
    }
  }

2. onscroll 이벤트를 위와같이 수정해준다 .

  1. scrollTop + clientHeight >= scrollHeight - 5 일때마다, page++ , total을 10씩 증가시켜준다.
  2. total === end 일때, 종료시킨다.
  • 확인해보면 이제 더이상 스크롤을 해도 불러오지 않는것이 확인된다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

마지막작업 → 다 불러온 후에는 scroll 이벤트를 제거해준다.

  • 끝까지 닿으면, scroll 이벤트는 더이상 사용되지 않을 것이기 떄문이다.
const onScroll = () => {
    const { scrollTop, scrollHeight, clientHeight } = document.documentElement

    if (total === end) {
      window.removeEventListener('scroll', onScroll)

      return
    }
    if (scrollTop + clientHeight >= scrollHeight - 5) {
      page++
      total += 10
      loadPost()
    }
  }

 

 


 

추가작업 → loading 애니메이션 노출시키기

 

<body>
    <div class="wrap">
      <h2>Infinite Scroll</h2>
      <div class="posts"></div>
      <div class="loader">
        <div></div>
        <div></div>
        <div></div>
        <div></div>
      </div>
    </div>
    <script src="script.js"></script>
  </body>
  1. index.html을 보면 loader라는 div가있다.
    • opacity가 0으로 되어있다.

이것을 loading을할떄마다 opactiy를 1로만들어 노출시켜줄것이다.

 

const $loader = get('.loader')

const loadPost = async () => {
    // 로딩 엘리먼트를 보여줌
    showLoader()
    try {
      const response = await getPost()
      showPosts(response)
    } catch (error) {
      console.error(error)
    } finally {
      //로딩 엘리먼트를 사라지게 함
      hideLoader()
    }
  }
  1. loadPost를 try ~ catch ~ finally 구문으로 감싸준다.
  2. const $loader = get('.loader') → loader를 가져와준다.
.loader.show {
  opacity: 1;
}

css 파일에 loader에 show 클래스가 붙으면 opacity가 1이되도록 작성해놓는다.

 

const hideLoader = () => {
    $loader.classList.remove('show')
  }

  const showLoader = () => {
    $loader.classList.add('show')
  }

확인해보면, 스크롤되서 화면이 로드시 애니메이션이 잘 적용되는것을 확인할 수 있다.