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)
}
我正在做一个 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)
}