Descrição do Problema
Carregar a próxima página de dados quando a página rola para o final é um problema muito comum no desenvolvimento, e alguns plug-ins e componentes forneceram soluções correspondentes.
No entanto, se você não usa esses plug-ins ou componentes e deseja implementar as funções correspondentes, a solução geral é calcular dinamicamente a posição da barra de rolagem e solicitar a próxima página de dados quando a barra de rolagem atingir o fundo.
Esta é a solução alternativa que a maioria das pessoas usa. Mas as desvantagens desse esquema também são muito óbvias. Esse é o problema da sobrecarga de desempenho. Imagine que temos que calcular a posição quando a barra de rolagem está rolando, obter o elemento dom, obter a altura e então... Resumindo, a lógica ainda é relativamente complicada e o o acionamento do evento de rolagem monitorado é muito frequente, a rolagem acionará a lógica e, às vezes, os eventos de rolagem serão acionados centenas de vezes. Portanto, ouvir os eventos de rolagem e, em seguida, calcular dinamicamente a altura consome muito desempenho.
solução
Portanto, existe alguma boa maneira de atender às nossas necessidades e, ao mesmo tempo, reduzir a sobrecarga de desempenho. É sobre isso que vou falar hoje usando o padrão Observer.
Modo observador, na verdade, você provavelmente pode entender o que significa olhando para o nome. Suponha que haja um observador para nos ajudar a observar se a página chegou ao fundo. Quando a página chega ao fundo, ele pode nos lembrar que a página chegou ao fundo e, em seguida, executaremos o método correspondente, para reduzir os cálculos dinâmicos repetidos durante rolagem? E a sobrecarga desnecessária causada pela altura? Então como criar um observador, antes de criar um observador, primeiro precisamos conhecer uma api. Observador de Interseção
Qual é a utilidade desta API? Vamos dar uma olhada no que está escrito no documento oficial.
Observador de Interseção
O endereço do documento oficial:
https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver
Como você pode ver na documentação, o sectionObserver fornece uma maneira de observar o estado de interseção de um elemento de destino com seus elementos ancestrais ou a janela de visualização do documento de nível superior. Simplificando, ele permite rastrear se o elemento de destino se sobrepõe a seus ancestrais. Para um exemplo, veja a figura abaixo.
Supondo que a caixa vermelha represente nossa página e a caixa branca represente o elemento que observamos, aplique a intersecçãoObserver aqui, então definimos o elemento ancestral para a caixa vermelha neste momento. Essa API pode nos informar o status de sobreposição da caixa branca e da caixa vermelha. De acordo com esse recurso, podemos realizar o requisito de carregar a próxima página ao chegar na parte inferior. Então, como alcançá-lo?
Implementação
Suponha que exista tal página, colocamos uma caixa de carregamento na parte inferior da página. Neste ponto, só precisamos saber se a caixa de carregamento aparece no viewport, quando ela aparecer no viewport, basta carregarmos os dados da próxima página.
Então como saber que esse elemento aparece no viewport sem monitorar a barra de rolagem e calcular a altura. Neste momento, precisamos convidar nosso sectionObserver. O método de escrita específico é o seguinte
const ob = new IntersectionObserver(fuction(){
},
{
root:'视口',//目标元素
threshold:1
})
Ele retorna um objeto observador ob e a API recebe dois parâmetros, o primeiro parâmetro é a função de retorno de chamada. Ou seja, quando nossos elementos observados e alvo se sobrepõem, o callback de execução, geralmente, o método de solicitação dos dados da próxima página deve ser escrito nele. O segundo parâmetro é um objeto de configuração, que possui um atributo raiz, que pode especificar nosso elemento de destino. Este é um item preenchível. Quando não preenchido, ele define nossa viewport como o alvo por padrão, e nossa demanda também é direcionada para a viewport, então pode ser deixada em branco aqui. E também tem um segundo atributo, o papel do threshold é especificar quanto do nosso elemento observado se sobrepõe ao elemento alvo, para acionar o callback, ele pode preencher um decimal, ou um inteiro 1, quando preenchido Quando o inteiro é 1, isso significa que nosso elemento observado deve se sobrepor completamente à viewport para acionar o callback, que é a seguinte situação.
Então vamos melhorar o código
const ob = new IntersectionObserver(
() => {
console.log("被观察者完全出现在和视口重叠")
},
{
threshold: 1,
},
)
Então, como especificamos que o objeto observado é o carregamento na parte inferior? Conforme mencionado anteriormente, esta API retornará um objeto observador. Após obtermos o elemento de carregamento, podemos usar o objeto observador retornado para observar o carregamento
const loading = document.getElementById('loading')
const ob = new IntersectionObserver(
() => {
console.log("被观察者完全出现在和视口重叠")
},
{
threshold: 1,
},
)
ob.observe(loading)
Neste momento, é realmente possível observar se o carregamento se sobrepõe ao viewport. Em seguida, executamos a seguinte lógica para permitir a observação quando a página é inicializada. pode ser visto:
Você pode ver que esta frase já foi impressa na página, mas alguns amigos descobrirão que a lógica do código é executada duas vezes, então por que é executada duas vezes?
Em primeiro lugar, sabemos que o IntersectionObserver não executa a lógica até que o elemento atualmente observado esteja na viewport, mas enquanto ele se sobrepõe à viewport, ele executará a lógica e a sobreposição ocorrerá se ele entrar na viewport ou sai da janela de visualização. aciona a sobreposição. Podemos ver a imagem abaixo:
Quando a interface não responde (o timer aqui utilizado simula o retorno assíncrono da interface), não há dados na lista. Não há dúvida de que o carregamento neste momento se sobrepõe completamente à viewport. E quando a requisição da interface voltar:
Quando a interface solicita dados de volta, a lista reduz o carregamento, fora da área visível. O IntersectionObserver observa o estado de interseção do objeto observado e o elemento de destino. Portanto, se ele entra no viewport ou é espremido para fora do viewport, há um momento de interseção com o viewport, então ele é executado duas vezes.
Mas isso está errado, para solicitar a próxima página de dados quando a página chega ao fundo, na verdade só precisamos carregar para acionar a lógica ao entrar na página. Ao sair, na verdade não há necessidade de acionar a lógica. Então, como limitar isso.
Na verdade, existe um parâmetro na função callback, já que vários elementos podem ser observados, trata-se de um array, que armazena as informações dos elementos observados.
const loading = document.getElementById('loading')
const ob = new IntersectionObserver(
(target) => {
console.log(target)
},
{
threshold: 1,
},
)
ob.observe(loading)
Vamos imprimir este array, e podemos ver que existe um atributo isIntersecting nele. Quando for false, significa que o elemento está saindo da área visível, quando for true, significa que o elemento está entrando na área visível. Podemos usar este atributo para descartar a situação de sair da área visível.
const loading = document.getElementById('loading')
const ob = new IntersectionObserver(
(target) => {
if (target[0].isIntersecting) {
console.log("加载更多")
}
},
{
threshold: 1,
},
)
ob.observe(loading)
Então podemos ver que a função foi basicamente realizada, ou seja, a próxima página é carregada quando é puxada para baixo.
Mas ainda há um pequeno problema que se eu rolar para cima e para baixo repetidamente, isso será repetido muitas vezes, então precisamos adicionar um limite aqui, quando a interface é solicitada, um botão é ativado e quando a interface responde Para redefinir a chave, quando a chave estiver ligada, não execute a lógica. Neste momento, os requisitos podem ser perfeitamente realizados. O último ponto é que quando a última página de dados for exibida, basta remover o carregamento da página.
const loading = document.getElementById('loading')
const ob = new IntersectionObserver(
(target) => {
if (target[0].isIntersecting&&!state.loading) {
console.log("加载更多")
}
},
{
threshold: 1,
},
)
ob.observe(loading)
O conteúdo acima é todo o conteúdo da solicitação da próxima página de dados usando o modo observador. Essa maneira de escrever não é apenas simples, mas também pode reduzir bastante o consumo de desempenho. Você pode tentar escrevê-lo sozinho. A seguir, vou colar o código completo desta página para que todos copiem.
código completo
<template>
<div class="warp">
<van-button @click="router.back()">返回</van-button>
<div class="scrollContainer">
<div class="item" v-for="(item, index) in data" :key="index">
{
{
index }}
</div>
<div class="loading" id="loading" v-if="showL">
<i class="iconfont icon-jiazai"></i>
</div>
</div>
</div>
</template>
<script lang="ts">
import {
defineComponent, reactive, toRefs, onMounted, nextTick } from 'vue'
import {
router } from '@/router'
export default defineComponent({
name: 'LoadMore',
props: {
},
components: {
},
setup() {
const state = reactive({
data: new Array(0),
loading: false,
flag: 0,
showL: true,
})
const loadMore = () => {
return new Promise((resolve, reject) => {
try {
setTimeout(() => {
state.data = state.data.concat(new Array(20))
resolve(true)
}, 1500)
} catch (error) {
reject(error)
}
})
}
onMounted(() => {
nextTick(() => {
// Ts 非空断言 !
const loading: HTMLElement = document.getElementById('loading')!
const ob = new IntersectionObserver(
(target: Array<{
isIntersecting: boolean }>) => {
if (target[0].isIntersecting && !state.loading) {
state.loading = true
state.flag++
loadMore().then((res) => {
state.loading = false
if (state.flag >= 3) {
// 最后一页数据的时候移除掉loading
state.showL = false
}
})
}
},
{
threshold: 1,
},
)
ob.observe(loading)
})
})
return {
router,
...toRefs(state),
}
},
})
</script>
<style lang="scss" scoped>
@keyframes rotate {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
.warp {
flex-direction: column;
display: flex;
width: 100%;
height: 100%;
.scrollContainer {
flex: 1;
overflow-y: auto;
.item {
color: #fff;
display: flex;
align-items: center;
justify-content: center;
height: 100px;
background-color: rgb(39, 1, 29);
border-bottom: 1px solid #fff;
}
.loading {
margin: 10px 0;
display: flex;
align-items: center;
justify-content: center;
i {
animation: rotate 1s infinite linear;
}
}
}
}
</style>