Complexidades de um botão de rolagem infinito

Texto longo, leia o resumo: Reutilize seus elementos DOM e remova os que estão distantes de janela de visualização. Use marcadores de posição para considerar os dados atrasados. Confira demo e o código da função botão de rolagem.

Telas de rolagem infinita aparecem em toda a Internet. A lista de artistas do Google Music é Primeiro, a linha do tempo do Facebook é uma e o feed ao vivo do Twitter também. Você role para baixo e, antes de chegar ao fim, novos conteúdos aparecem em um passe de mágica. do nada. É uma experiência perfeita para os usuários, e é fácil acessar a contestação.

No entanto, o desafio técnico por trás de um botão de rolagem infinito é mais difícil do que parece. A variedade de problemas que você encontra quando quer fazer a coisa certaTM é vasta. Tudo começa com coisas simples, como os links no rodapé se tornando fica praticamente inacessível porque o conteúdo continua empurrando o rodapé. Mas o e os problemas ficam mais difíceis. Como você lida com um evento de redimensionamento quando alguém gira o do smartphone do modo retrato para o modo paisagem ou como evitar que ele fique desgastado quando a lista fica longa demais?

A coisa certaTM

Pensamos que isso era motivo suficiente para criar uma implementação de referência que mostra uma maneira de resolver todos esses problemas de forma reutilizável, manter os padrões de desempenho.

Usaremos três técnicas para atingir nosso objetivo: reciclagem de DOM e lápides e a âncora de rolagem.

Nosso caso de demonstração será uma janela de chat parecida com o Hangouts, onde podemos rolar pelas mensagens. Para começar, precisamos de uma fonte infinita de chat. e envio de mensagens. Tecnicamente, nenhum dos controles de rolagem infinitos existe realmente infinitos, mas com a quantidade de dados disponíveis para serem bombeados o que provavelmente seria necessário. Para simplificar, vamos apenas codificar um conjunto de mensagens de bate-papo e escolher mensagem, autor e anexo de imagem de vez em quando em aleatória, com um pouco de atraso artificial, para se comportar como o em uma rede real.

Captura de tela do app do Chat

Reciclagem de DOM

A reciclagem do DOM é uma técnica subutilizada para manter baixa a contagem de nós do DOM. A A ideia geral é usar elementos DOM já criados que estão fora da tela da criação de novas. É claro que os nós do DOM em si são baratos, mas porque cada um deles acrescenta um custo extra de memória, layout, estilo e pintura. Dispositivos mais simples ficarão visivelmente mais lentos se não forem completamente inutilizáveis se a site tem um DOM muito grande para gerenciar. Lembre-se também de que toda mudança de layout e reaplicação de seus estilos, um processo que é acionado sempre que uma classe seja adicionado ou removido de um nó - fica mais caro com um DOM maior. Reciclar os nós DOM significa que manteremos o número total de elementos DOM nós significativamente menores, tornando todos esses processos mais rápidos.

O primeiro obstáculo é a própria rolagem. Como teremos apenas um pequeno subconjunto de todos os itens disponíveis no DOM a qualquer momento, precisamos encontrar outra forma para que a barra de rolagem do navegador reflita corretamente a quantidade de conteúdo teoricamente. Usaremos um elemento de sentinela de 1 x 1 pixel com uma transformação. forçar o elemento que contém os itens (a passarela) a ter o resultado a altura. Vamos promover cada elemento na passarela à sua própria camada para tornar que a camada da pista esteja completamente vazia. Sem cor de fundo nada. Se a camada da pista não estiver vazia, ela não estará qualificada para e teremos que armazenar uma textura em nossa placa gráfica que tenha com algumas centenas de milhares de pixels de altura. Definitivamente não é viável em uma dispositivo móvel.

Sempre que rolarmos, verificaremos se a janela de visualização se aproximou suficientemente o fim da pista. Nesse caso, estenderemos a pista movendo a sentinela e mover os itens que deixaram a janela de visualização para a parte inferior do e preenchê-las com novos conteúdos.

Pista Sentinel Janela de visualização

O mesmo vale para a rolagem na outra direção. No entanto, nunca vamos reduzir a pista na nossa implementação para que a posição da barra de rolagem permaneça consistentes.

Lápides

Como mencionamos anteriormente, tentamos fazer com que nossa fonte de dados se comporte como algo no no mundo real. com latência de rede e muito mais. Isso significa que, se nossos os usuários fazem uso da rolagem, eles podem rolar facilmente além do último elemento para os quais temos dados. Se isso acontecer, colocaremos um item de tombstone, um marcador de posição: que será substituído pelo item com conteúdo real assim que o os dados chegaram. As lápides também são recicladas e têm uma piscina separada para e reutilizáveis. Precisamos disso para que possamos fazer uma boa transição tombstone ao item preenchido com conteúdo, que de outra forma seria muito desagradável para o usuário e pode até fazer com que ele perca o controle focar.

Essas
tumba Muito pedra. Uau.

Um desafio interessante aqui é que itens reais podem ter uma altura maior do que o item de marca de exclusão devido a diferentes quantidades de texto por item ou um anexo imagem. Para resolver isso, vamos ajustar a posição de rolagem atual todas as vezes os dados chegam e uma tombstone é substituída acima da janela de visualização, ancorando a posição de rolagem a um elemento em vez de um valor de pixel. Esse conceito é chamada de ancoragem de rolagem.

Ancoragem de rolagem

Nossa ancoragem de rolagem será invocada quando as tombstones forem substituídas ou assim como quando a janela é redimensionada (o que também acontece quando os dispositivos são ser virada!). Teremos que descobrir qual é o elemento mais visível em da janela de visualização. Como esse elemento só pode ficar parcialmente visível, também armazenar o deslocamento a partir da parte superior do elemento onde a janela de visualização começa.

Diagrama de ancoragem de rolagem.

Se a janela de visualização for redimensionada e a pista tiver alterações, poderemos restaurar uma situação visualmente idêntica ao usuário. Você ganhou! Exceto um modelo significa que a altura de cada item foi alterada. Como sabe até que ponto o conteúdo ancorado deve ser colocado? Nós não! Para descobrir teríamos que definir o layout de cada elemento acima do item ancorado e somar todos alturas; isso pode causar uma pausa significativa após um redimensionamento, e não querem isso. Em vez disso, recorremos à premissa de que cada item acima tem o mesmo tamanho como uma tombstone e ajustar nossa posição de rolagem. Como os elementos são rolamos até a passarela, ajustamos a posição de rolagem, adiando a leitura o layout funcionar quando for realmente necessário.

Layout

Pulei um detalhe importante: o layout. Cada reciclagem de um elemento DOM normalmente mudaria o layout de toda a pista, o que nos deixaria bem abaixo de desejada de 60 quadros por segundo. Para evitar isso, estamos assumindo o ônus o layout em nós mesmos e usar elementos absolutamente posicionados com as transformações. Dessa forma, podemos fingir que todos os elementos mais acima na pista ainda estão ocupando espaço quando, na realidade, há apenas espaço vazio. Como estamos fazendo podemos armazenar em cache as posições em que cada item acaba e podemos carregar imediatamente o elemento correto do cache quando o usuário rolar para trás.

O ideal é que os itens só sejam repintados uma vez quando forem anexados ao DOM e ser inabalável com a adição ou remoção de outros itens na passarela. Ou seja, possível, mas apenas com navegadores modernos.

Ajustes de vanguarda

Recentemente, o Chrome adicionou suporte para Contenção de CSS, um recurso que permite aos desenvolvedores informar ao navegador que um elemento é um limite para o layout e os trabalhos de pintura. Como estamos criando o layout aqui, é um ótimo aplicação para contenção. Sempre que adicionamos um elemento à passarela, sabemos os outros itens não precisam ser afetados pelo novo layout. Portanto, cada item deve e receba contain: layout. Também não queremos afetar o restante do nosso site, então a própria pista também precisa receber essa diretiva de estilo.

Também consideramos o uso IntersectionObservers como um mecanismo para detectar quando o usuário rolou a tela o suficiente para começarmos a reciclar elementos e carregar novos dados. No entanto, IntersectionObservers são especificados para serem de alta latência (como se usando requestIdleCallback), então talvez ficamos menos responsivos com IntersectionObservers. Até mesmo a implementação atual que usa scroll tem esse problema, já que os eventos de rolagem são enviados em uma com base no “melhor esforço”. Por fim, o worklet de composição de Houdini (em inglês) seria a solução de alta fidelidade para esse problema.

Ainda não é perfeito

Nossa implementação atual de reciclagem do DOM não é ideal, pois acrescenta todos os elementos que passam pela janela de visualização, em vez de se preocupar apenas com as que estão realmente na tela. Isso significa que, quando você rola realmente rápido, coloca tanto trabalho no layout e na pintura no Chrome que ele não consegue acompanhar. Você encerrará vendo nada além do plano de fundo. Não é o fim do mundo, com certeza tem algo a ser melhorado.

Esperamos que você saiba como problemas simples e desafiadores podem se tornar quando deseja que combinam uma ótima experiência do usuário com altos padrões de desempenho. Com Os Progressive Web Apps estão se tornando experiências essenciais em celulares, e isso se tornarão mais importantes, e os desenvolvedores web terão que continuar investindo usando padrões que respeitam as restrições de desempenho.

Você encontra todos os códigos no nosso repositório. Conseguimos que é possível mantê-lo reutilizável, mas não o publicaremos como uma biblioteca real npm ou como um repositório separado. O uso principal é educacional.