Como o front-end usa o padrão de design do observador para rolar a página até o final para solicitar a próxima página de dados

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.
insira a descrição da imagem aqui

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.
insira a descrição da imagem aqui

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.

insira a descrição da imagem aqui

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.
insira a descrição da imagem aqui

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:
insira a descrição da imagem aqui

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:
insira a descrição da imagem aqui

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:
insira a descrição da imagem aqui

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)

insira a descrição da imagem aqui

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>

Acho que você gosta

Origin blog.csdn.net/yangxbai/article/details/126070465
Recomendado
Clasificación