Reputation: 135
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 "DOMContentLoaded", but not sure. Thank you.
Here is my codepen 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>
Upvotes: 0
Views: 858
Reputation: 24681
First of all please don't use XMLHttpRequest
it's very old and hard to use and read. Use fetch instead.
Your problem is caused by the fact that you are creating one XMLHttpRequest
. So before the first request is completed the second request starts and cancel the first one. Just move const req = new XMLHttpRequest();
to getResp
.
But I would just change getResp
to:
function getResp(url, callback) {
fetch(url, {
headers: {
'Client-ID': clientID,
'Accept': 'application/vnd.twitchtv.v5+json'
}
})
.then(response => response.json())
.then(callback)
.catch(showError)
}
Upvotes: 1