Intersection Observer API 用于无限滚动

Intersection Observer API for infinite scrolling

我正在做一个 twitch 网络应用程序。我尝试使用 Intersection observer API 来实现无限滚动并且它有效。但是,我注意到当无限滚动工作时,我的导航栏完全消失了。我猜这个问题是由“DOMContentLoaded”引起的,但不确定。谢谢。

这是我的代码笔 link : Twitch

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 >= 200 && req.status < 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', ()=> {
  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}&limit=20&offset=${offset}`
  return streamUrl
}

getResp(topGameurl, (data) => {
  const topGames = [...data.top]
  const result = topGames.reduce((result, item) => {
    result += `<li>${item.game.name}</li>`
    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 => {
  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) => {
    const dataArrs = [...data.streams]
    dataArrs.map(dataArr => {
      let streamItem = document.createElement('div')
      streamItem.classList.add('stream')
      streamItem.innerHTML = `
      <p class="viewers">Viewers: ${dataArr.viewers}</p>
      <img src="${dataArr.preview.large}" alt="" class="preview">
      <div class="streamer">
        <img src="${dataArr.channel.logo}" alt="" class="logo">
        <p class="name">${dataArr.channel.name}</p>
        <p class="lang">${dataArr.channel.broadcaster_language.toUpperCase()}</p>
      </div>
    `
      streamItems.appendChild(streamItem)
    })
  })
}
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 > .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;
  }

  
}
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="reset.css">
  <link rel="stylesheet" href="style.css">
  <title>Twitch</title>
</head>

<body>
  <div class="container">
    <header id="header">
      <label for="check__status" class="rwdSwitch"><i class="fas fa-bars"></i></label>
      <input type="checkbox" class="check" id="check__status">
      <h1 class="title">Twitch Top Games</h1>
      <nav class="navbar">
        <ul class="nav__list"></ul>
      </nav>
    </header>
    <div class="selections">
      <label for="language" class="lang__title">Filter by Language: </label>
      <select name="lang" id="language" class="langFilter"></select>
    </div>
    <main class="stream_box">
      <p class="gameTitle"></p>
      <div class="streamItems"></div>
    </main>
    <div class="stream-end"></div>
  </div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/js/all.min.js"></script>
  <script src="api.js"></script>
  <script src="app.js"></script>
</body>

</html>

首先请不要使用XMLHttpRequest它太老了,难以使用和阅读。请改用 fetch

您的问题是因为您正在创建一个 XMLHttpRequest。因此,在第一个请求完成之前,第二个请求开始并取消第一个请求。只需将 const req = new XMLHttpRequest(); 移动到 getResp.

但我只想将 getResp 更改为:

function getResp(url, callback) {
  fetch(url, {
    headers: {
      'Client-ID': clientID,
      'Accept': 'application/vnd.twitchtv.v5+json'
    }
  })
  .then(response => response.json())
  .then(callback)
  .catch(showError)
}