title: Intersection Observer API for infinite scrolling
body: <p>I am doing a twitch web app. I try to use the Intersection observer API to achieve infinite scrolling and it works. However, I notice that my navbar just disappears at all when the infinite scrolling is working. I guess the issue is caused by &quot;DOMContentLoaded&quot;, but not sure. Thank you.</p>
  <p>Here is my codepen link : <a href="https://codepen.io/oscar-wang13/pen/YzqNZzx" rel="nofollow noreferrer">Twitch</a></p>
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>const clientID = '5npghe3kytuifte3z9kvwnto50mqch';
  const req = new XMLHttpRequest();
  
  function showError() {
    alert('Error');
  }
  
  function getResp(url, callback) {
    req.open('GET', url, true);
    req.setRequestHeader('Client-ID', clientID);
    req.setRequestHeader('Accept', 'application/vnd.twitchtv.v5+json');
    req.send();
    req.onload = function () {
      if (req.status &gt;= 200 &amp;&amp; req.status &lt; 400) {
        let data
        try {
          data = JSON.parse(req.response)
        } catch (err) {
          showError();
          return;
        }
        callback(data)
      } else {
        showError();
      }
    }
  }
  
  const navList = document.querySelector('.nav__list')
  const streamBox = document.querySelector('.stream_box')
  const streamItems = document.querySelector('.streamItems')
  const langFilter = document.querySelector('.langFilter')
  const langArr = ['ALL', 'EN', 'ZH', 'ES', 'FR', 'DE', 'RU', 'KO', 'JA', 'PT', 'AR'];
  const urlRoot = 'https://api.twitch.tv/kraken/'
  const topGameurl = `${urlRoot}games/top?limit=5`
  const streamApi = `${urlRoot}streams/`
  let offset = 0
  
  
  document.addEventListener('DOMContentLoaded', ()=&gt; {
    const target = document.querySelector('.stream-end')
    let options = {
      root: null,
      rootMargin: '30px', // looking entire viewport
      threshold: 0.5, // if 50% of footer
    }
    const observer = new IntersectionObserver(handleIntersection, options)
    observer.observe(target)
  })
  
  function handleIntersection(entries) {
    if (entries[0].isIntersecting) {
      let gameTitle = document.querySelector('.gameTitle')
      let gameURLname = encodeURIComponent(gameTitle.innerHTML)
      const loadmorestreamUrl = createURL(streamApi, gameURLname, offset)
      offset += 100
      getData(loadmorestreamUrl)
    }
  }
  
  
  function createURL(url, game, offset) {
    const streamUrl = `${url}?game=${game}&amp;limit=20&amp;offset=${offset}`
    return streamUrl
  }
  
  getResp(topGameurl, (data) =&gt; {
    const topGames = [...data.top]
    const result = topGames.reduce((result, item) =&gt; {
      result += `&lt;li&gt;${item.game.name}&lt;/li&gt;`
      return result
    }, '')
    navList.innerHTML = result
    const gameName = encodeURIComponent(data.top[0].game.name)
    const streamUrl = createURL(streamApi, gameName, offset)
    getData(streamUrl)
  })
  
  navList.addEventListener('click', e =&gt; {
    streamItems.innerHTML = ''
    let gameTitle = document.querySelector('.gameTitle')
    const gameName = e.target.innerHTML
    gameTitle.innerHTML = gameName
    const gameNameURL = encodeURIComponent(gameName)
    const streamUrl = createURL(streamApi, gameNameURL, offset)
    getData(streamUrl)
  })
  
  
  function getData(url) {
    getResp(url, (data) =&gt; {
      const dataArrs = [...data.streams]
      dataArrs.map(dataArr =&gt; {
        let streamItem = document.createElement('div')
        streamItem.classList.add('stream')
        streamItem.innerHTML = `
        &lt;p class="viewers"&gt;Viewers: ${dataArr.viewers}&lt;/p&gt;
        &lt;img src="${dataArr.preview.large}" alt="" class="preview"&gt;
        &lt;div class="streamer"&gt;
          &lt;img src="${dataArr.channel.logo}" alt="" class="logo"&gt;
          &lt;p class="name"&gt;${dataArr.channel.name}&lt;/p&gt;
          &lt;p class="lang"&gt;${dataArr.channel.broadcaster_language.toUpperCase()}&lt;/p&gt;
        &lt;/div&gt;
      `
        streamItems.appendChild(streamItem)
      })
    })
  }</code></pre>
  <pre class="snippet-code-css lang-css prettyprint-override"><code>html, body {
    font-size: 16px;
    box-sizing: border-box;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background:black;
    background-attachment: fixed;
    
  }
  
  .container {
    width: 90%;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    padding: 2rem;
    overflow-x: hidden;
  }
  
  #header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    color: #fff;
    margin-bottom: 1.2rem;
  }
  
  .navbar {
    position: relative;
    transform: translateX(0%);
  }
  
  .title {
    margin-right: 1rem;
    font-size: 2rem;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    font-weight: 700;
    color: #741cf7;
  }
  
  .nav__list {
    display: flex;
  }
  
  .nav__list li {
    padding: .8rem .6rem;
    cursor: pointer;
    font-size: 1rem;
    font-weight: 700;
  }
  
  .nav__list li:hover {
    border-radius: .5rem;
    background: #a87ceb;
    transition: background .3s ease;
  }
  
  .nav__list li + li {
    margin-left: 1rem;
  }
  
  #stream_box {
    display: flex;
    flex-direction: column;
    border-radius: 8px;
    align-items: center;
    padding: 1.5rem;
    color: #fff;
    position: relative;
  }
  
  .game__title {
    font-size: 2rem;
    margin-bottom: 1rem;
    font-weight: 600;
    color:#741cf7;
  }
  
  .top__twenty {
    font-size: 1.4rem;
    margin-bottom: 1.5rem;
  }
  
  .streamItems {
    display: flex;
    flex-flow: row wrap;
    width: 100%;
    justify-content: center;
  }
  
  .lang__options {
    position: absolute;
    top: 2rem;
    right: 7rem;
  }
  
  .lang__title {
    font-size: 1.2rem;
    color: #fff;
  }
  
  #language {
    outline: none;
    width: 3rem;
    background: transparent;
    color: #741cf7;
    border: none;
    font-size: 1rem;
  }
  
  .stream {
    width: 30%;
    margin: 1.5rem;
    background-color: rgba(255, 255, 255, .15);
    backdrop-filter: blur(5px);
    cursor: pointer;
  }
  
  .preview {
    width: 100%;
    vertical-align: middle;
  }
  
  .viewers {
    display: none;
  }
  
  .stream:hover &gt; .viewers {
    display: block;
    position: absolute;
    z-index: -1;
    animation-name: move;
    animation-duration: .4s;
    animation-timing-function: ease;
    animation-fill-mode: forwards;
  }
  
  @keyframes move {
    from {
      top: 0px;
    }
    to {
      top: -20px;
    }
  }
  
  .streamer {
    display: flex;
    align-items: center;
    padding: .5rem;
    position: relative;
    color: #fff;
  }
  
  .logo {
    width: 15%;
    border-radius: 50%;
    margin-right: .8rem;
  }
  
  .lang {
    position: absolute;
    right: .5rem;
    bottom: .5rem;
  }
  
  .hidden {
    display: none;
  }
  
  .check {
    display: none;
  }
  
  .rwdSwitch {
    display: none;
  }
  
  .gameTitle {
    color: #fff;
  }
  
  
  @media screen and (max-width: 1024px) {
    .lang__options {
      position: absolute;
      top: 7rem;
      right: 50%;
      transform: translateX(50%);
    }
  
    .streamItems {
      flex-flow: row wrap;
      width: 100%;
      justify-content: center;
      margin-top: 1.2rem;
    }
  
    .stream {
      width: 100%;
    }
  
    .nav__list li {
      text-align: center;
      font-size: 1rem;
    }
  }
  
  @media screen and (max-width: 576px) {
    body {
      overflow-x: hidden;
    }
    .navbar {
      position: absolute;
      top: 20%;
      right: 50%;
      transform: translateX(200%);
      transition: transform .3s ease-in-out;
      background-color: rgba(255, 255, 255, .15);
      backdrop-filter: blur(5px);
      z-index: 999;
      visibility: hidden;
    }
  
    .top__twenty {
      font-size: .8rem;
    }
  
    .nav__list {
      flex-direction: column;
      align-items: flex-end;
    }
  
    .rwdSwitch {
      display: block;
      cursor: pointer;
      position: absolute;
      right:1.5rem;
      top: 2.5rem;
      z-index: 999;
    }
  
    .check:checked ~ .navbar {
      visibility: visible;
      transform: translateX(100%);
      transition: transform .3s ease-in-out;
    }
  
    
  }</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    &lt;link rel="stylesheet" href="reset.css"&gt;
    &lt;link rel="stylesheet" href="style.css"&gt;
    &lt;title&gt;Twitch&lt;/title&gt;
  &lt;/head&gt;
  
  &lt;body&gt;
    &lt;div class="container"&gt;
      &lt;header id="header"&gt;
        &lt;label for="check__status" class="rwdSwitch"&gt;&lt;i class="fas fa-bars"&gt;&lt;/i&gt;&lt;/label&gt;
        &lt;input type="checkbox" class="check" id="check__status"&gt;
        &lt;h1 class="title"&gt;Twitch Top Games&lt;/h1&gt;
        &lt;nav class="navbar"&gt;
          &lt;ul class="nav__list"&gt;&lt;/ul&gt;
        &lt;/nav&gt;
      &lt;/header&gt;
      &lt;div class="selections"&gt;
        &lt;label for="language" class="lang__title"&gt;Filter by Language: &lt;/label&gt;
        &lt;select name="lang" id="language" class="langFilter"&gt;&lt;/select&gt;
      &lt;/div&gt;
      &lt;main class="stream_box"&gt;
        &lt;p class="gameTitle"&gt;&lt;/p&gt;
        &lt;div class="streamItems"&gt;&lt;/div&gt;
      &lt;/main&gt;
      &lt;div class="stream-end"&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/js/all.min.js"&gt;&lt;/script&gt;
    &lt;script src="api.js"&gt;&lt;/script&gt;
    &lt;script src="app.js"&gt;&lt;/script&gt;
  &lt;/body&gt;
  
  &lt;/html&gt;</code></pre>
  </div>
  </div>
  </p>