Я должен сделать запрос к API, когда window.scrollTop < 20
. В случае успеха я добавляю новый дочерний компонент к вершине .list
компонента.
Я использую throttle
, чтобы предотвратить множественные вызовы API, которые происходят из-за инерции прокрутки. Это хорошо работает, если длительность выборки близка к длительность дросселя и оба значения больше 800 (я не уверен, почему это работает хорошо только тогда, когда продолжительность больше 800, не обязательно, но работаетхорошо для большой продолжительности). В противном случае это не работает - скажем, длительность близка к 300.
Поведение -
- из-за прокрутки по инерции, окно продолжает прокручиваться после успешной выборки и
window.scrollTop
еще раз меньше 20
, что делает еще один вызов API. или window.scrollTop
меньше 20 и полоса прокрутки достигает вершины.
Параметры, от которых зависит это поведение: :
- импульс прокрутки
- высота предварительно добавленного списка дочернего элемента.
- длительность газа
- длительность выборки
Кконец инерции прокрутки я пробовал overflow: hidden
. Это делает пользовательский интерфейс невосприимчивым к длительности выборки, что недопустимо.
Я сохраняю позицию scrollTop
, которая была во время запроса на выборку, для которой я выполняю window.scrollTo(0, scrollHeight - state.docScrollHeight)
. Я записываю позицию scrollTop во время запроса на выборку в state.docScrollHeight
.
Я хочу предотвратить несколько вызовов API и поддерживать позицию прокрутки.
Fiddle: https://jsfiddle.net/05mwb3hq/1/
Эта проблема лучше всего воспроизводится в инструменте разработчика.
var state = {
loadingMore: false,
docScrollHeight: 0,
lastCall: 0,
lastCallToFetch: 0,
}
var count = 0;
function createEl() {
let el = document.createElement('div');
el.classList.add('list-comp');
let elNum = document.createElement('span');
elNum.textContent = count;
elNum.classList.add('elNum');
el.appendChild(elNum);
const listEl = document.querySelector('#list');
listEl.prepend(el);
count++;
}
function mockApi(lagDuration) {
return new Promise(resolve => {
setTimeout(resolve, lagDuration)
});
}
function throttle(fetchCb, throttleDuration) {
let previousCall = state.lastCall;
state.lastCall = Date.now();
if (previousCall === undefined || (state.lastCall - previousCall) > throttleDuration) {
state.lastCallToFetch = Date.now();
state.loadingMore = true;
document.querySelector('.loader').classList.remove('d-none');
fetchCb(300)
.then(() => {
createEl();
state.loadingMore = false;
})
.then(() => {
const {
scrollHeight
} = document.documentElement;
if (!state.loadingMore) {
window.scrollTo(0, scrollHeight - state.docScrollHeight);
document.querySelector('.loader').classList.add('d-none');
}
});
}
}
var onScrollList = function on_scroll_list() {
if (window.scrollY < 20) {
if (!state.loadingMore) {
state.docScrollHeight = document.documentElement.scrollHeight;
state.loagingMore = true;
throttle(mockApi, 800);
}
}
}
window.addEventListener('scroll', onScrollList);
window.onload = function() {
window.scrollTo(0, document.body.clientHeight);
}
* {
box-sizing: border-box;
}
.d-none {
display: none;
}
#main-scrollable-comp {
overflow-y: scroll;
background-color: grey;
}
#default-comp {
background-color: red;
height: 800px;
}
.list-comp {
height: 200px;
width: 100%;
background-color: purple;
border: 2px solid yellow;
display: flex;
align-items: flex-end;
justify-content: center;
}
.elNum {
color: green;
font-size: 54px;
}
.loader-container {
height: 100px;
background-color: white;
display: flex;
align-items: center;
justify-content: center;
}
.loader {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 36px;
height: 36px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Smooth Scroll</title>
<link href="styles.css" rel="stylesheet">
</head>
<body>
<div id="main-scrollable-comp">
<div class='loader-container'>
<div class='loader d-none'></div>
</div>
<div id='list'></div>
<div id="default-comp"></div>
</div>
</body>
</html>