React-window: Compatibilidade com ScrollSync

Criado em 8 nov. 2018  ·  52Comentários  ·  Fonte: bvaughn/react-window

Tenho um caso de uso em que preciso criar uma grade com cabeçalhos fixos que ficam fixados no topo. No reat-virtualizado, isso pode ser realizado criando dois componentes de grade (um para cabeçalhos, um para o corpo da grade) e sincronizando a posição de rolagem para que a rolagem horizontal da grade de cabeçalho seja sincronizada com a grade principal.

Eu sei que você está tentando manter a janela de reação mais leve, tanto em termos de tamanho do pacote quanto de complexidade conceitual, em favor da construção de funcionalidades adicionais como pacotes separados. Essa é uma direção com a qual eu concordo. Eu acho que o componente ScrollSync do react-virtualized poderia ser extraído ou adaptado para funcionar com react-window. No entanto, para fazer isso, a react-window precisaria aceitar props para scrollLeft e scrollTop para que o deslocamento de rolagem da grade de cabeçalho pudesse ser gerenciado diretamente.

Este é um caso de uso que você gostaria de oferecer suporte? Se não, você tem algum conselho sobre que direção devo seguir para implementar isso sozinho?

Obrigado por você trabalhar nesta biblioteca. Como alguém que usou a virtualização de reação por alguns anos, aprecio a simplicidade de começar com a janela de reação e como ela tem sido excelente para mim sem muita intervenção manual.

Comentários muito úteis

Isso faz todo o sentido. Obrigado pela sugestão! Eu coloquei para funcionar e é muito fácil de configurar. Tão fácil que definitivamente não garante um pacote independente. Talvez um exemplo nos documentos, mas acho que é você quem decide. Vou deixar um link para meu exemplo de trabalho do Code Sandbox aqui, caso alguém mais se depare com esse problema no futuro.

https://codesandbox.io/s/y3pyp85zm1

TLDR - coloque um ref na grade do cabeçalho e coloque-o na grade do corpo.

onScroll={({ scrollLeft }) => this.headerGrid.current.scrollTo({ scrollLeft })}

Todos 52 comentários

Em primeiro lugar, obrigado pelas palavras amáveis ​​e feedback positivo. Fico feliz em saber que a janela de reação tem funcionado bem para você até agora!

Concordo que um componente como o ScrollSync pode ser lançado como um pacote autônomo que depende da janela de reação, mas não gostaria de adicioná-lo a este projeto, pois não é o núcleo do janelamento.

Com relação à sua pergunta específica sobre adereços de rolagem, esta não é uma mudança que eu estaria disposto a fazer no projeto, porque depois de usá-lo com o react-virtualized, percebi que ele tem algumas desvantagens sérias. Na verdade, eu escrevi sobre isso no blog React, se você estiver curioso:

https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#anti -pattern-erasing-state-when-props-change

Você pode usar a API de rolagem imperativa que o react-window oferece para obter um comportamento de sincronização semelhante. Você apenas teria que chamar esses métodos a partir dos ciclos de vida do commit, em vez de passar props.

Esperançosamente, isso faz sentido, mas sinta-se à vontade para fazer perguntas se não fizer isso!

Isso faz todo o sentido. Obrigado pela sugestão! Eu coloquei para funcionar e é muito fácil de configurar. Tão fácil que definitivamente não garante um pacote independente. Talvez um exemplo nos documentos, mas acho que é você quem decide. Vou deixar um link para meu exemplo de trabalho do Code Sandbox aqui, caso alguém mais se depare com esse problema no futuro.

https://codesandbox.io/s/y3pyp85zm1

TLDR - coloque um ref na grade do cabeçalho e coloque-o na grade do corpo.

onScroll={({ scrollLeft }) => this.headerGrid.current.scrollTo({ scrollLeft })}

Obrigado pelo link! Isso é muito atencioso.

Em quinta-feira, 8 de novembro de 2018, 13h09, Reagan Keeler < notificaçõ[email protected] escreveu:

Isso faz todo o sentido. Obrigado pela sugestão! Eu fiz funcionar,
e é muito fácil de configurar. Tão fácil que definitivamente não
garante um pacote autônomo. Talvez um exemplo nos documentos, mas esse é o seu
ligar, suponho. Vou deixar um link para meu exemplo de trabalho do Code Sandbox aqui
no caso de alguém mais se deparar com esse problema no futuro.

https://codesandbox.io/s/y3pyp85zm1

TLDR - coloque um ref na grade do cabeçalho e coloque-o na grade do corpo.

onScroll = {({scrollLeft}) => this.headerGrid.current.scrollTo ({scrollLeft})}

-
Você está recebendo isso porque modificou o estado abrir / fechar.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/bvaughn/react-window/issues/86#issuecomment-437156749 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AABznTUunzEIs6bVQfVsz7T21L2-Pkkoks5utJ17gaJpZM4YVMd7
.

Para qualquer um que se deparou com isso, eu tinha uma super grade congelada superior / direita / inferior / esquerda sincronizada com a rolagem em react-virtualized que acabei de migrar para esta biblioteca. Direi que a solução acima tem pelo menos o mesmo desempenho, senão melhor, e substancialmente menos dolorosa do que usar ScrollSync na outra biblioteca.

Ele também combina perfeitamente com o novo gancho useRef react (https://reactjs.org/docs/hooks-reference.html#useref)

const topRef = useRef();
const rightRef = useRef();
const bottomRef = useRef();
const leftRef = useRef();
...
<Grid
  onScroll={({ scrollLeft, scrollTop }) => {
    if (leftRef.current) {
      leftRef.current.scrollTo({ scrollTop });
    }
    if (rightRef.current) {
      rightRef.current.scrollTo({ scrollTop });
    }
    if (topRef.current) {
      topRef.current.scrollTo({ scrollLeft });
    }
    if (bottomRef.current) {
      bottomRef.current.scrollTo({ scrollLeft });
    }
  }}
  ...
/>

Agradável! Obrigado por compartilhar @ranneyd!

Ele também combina perfeitamente com o novo gancho useRef react (https://reactjs.org/docs/hooks-reference.html#useref)

Obrigado @ranneyd

Seria possível compartilhar um exemplo de trabalho?

@ranneyd obrigado novamente, segui sua abordagem e a sincronização de rolagem funciona bem.

O estouro oculto nas barras de rolagem causa desalinhamento próximo ao final da grade:

gridalignement

Alguma sugestão sobre como consertar isto?

Exemplo de CodeSandbox

desde já, obrigado

@carlosagsmendes é por causa da barra de rolagem. À esquerda, defina a altura como a altura menos o tamanho da barra de rolagem. Para obter consistência entre os dispositivos, codifico manualmente o tamanho da barra de rolagem com CSS. Se o seu conteúdo é tão dinâmico que pode ou não haver uma barra de rolagem, faça algo como "num items * size items <width (você deve ter todos esses valores ali)" e então mude a altura dependendo disso.

Para obter consistência entre os dispositivos, codifico manualmente o tamanho da barra de rolagem com CSS.

FWIW, o pacote dom-helpers tem uma função útil scrollbarSize que informa qual é a largura do dispositivo atual. Pode ser melhor do que codificar.

@bvaughn sim! Eu esqueci de mencionar isso. No entanto, não funcionou para nós. Acho que o problema é que já estávamos fazendo barras de rolagem codificadas e isso confundiu

Obrigado. Vou tentar!

Isso funciona com VariableSizeList horizontal? Eu tentei, mas meu navegador congela.

onScroll={({ scrollLeft }) => this.headerGrid.current.scrollTo({ scrollLeft })}

Funciona bem com VariableSizeGrid, mas meu cabeçalho fica um pouco para trás na rolagem.

@ajaymore então com relação ao cabeçalho atrasado: a sincronização da rolagem tem problemas quando você usa a roda de rolagem em um Mac. O MacOSX tem essa coisa de rolagem natural integrada, onde interpola a rolagem para torná-la "mais suave". A taxa de atualização é realmente mais rápida do que a taxa de quadros da animação no Chrome. Portanto, o elemento que você está rolando será animado antes que V8 / ScrollSync tenha tempo de atualizar o DOM. Esta é uma limitação técnica para a qual não encontrei solução.

Curiosidade: se você usar manualmente a barra de rolagem do navegador (como rolar à moda antiga arrastando uma coisa pequena), ela funcionará perfeitamente. A coisa de interpolação é construída na roda de rolagem / toque no trackpad

@ajaymore então com relação ao cabeçalho atrasado: a sincronização da rolagem tem problemas quando você usa a roda de rolagem em um Mac. O MacOSX tem essa coisa de rolagem natural integrada, onde interpola a rolagem para torná-la "mais suave". A taxa de atualização é realmente mais rápida do que a taxa de quadros da animação no Chrome. Portanto, o elemento que você está rolando será animado antes que V8 / ScrollSync tenha tempo de atualizar o DOM. Esta é uma limitação técnica para a qual não encontrei solução.

Curiosidade: se você usar manualmente a barra de rolagem do navegador (como rolar à moda antiga arrastando uma coisa pequena), ela funcionará perfeitamente. A coisa de interpolação é construída na roda de rolagem / toque no trackpad

@ranneyd Obrigado por uma resposta tão rápida. Eu concordo que é uma limitação. Funcionará bem na maioria dos dispositivos, portanto, não é uma grande preocupação.

@bvaughn , você já brincou com position: sticky ? Faz um tempo que não vejo isso e sei que o suporte do navegador é escasso, mas gostaria de saber se há uma maneira de aproveitá-lo em navegadores onde é compatível ...

Tenho o mesmo problema que @ajaymore , ou seja, a sincronização de dois componentes é demorada (conforme declarado, funciona bem na rolagem manual). Estou testando com o Chrome em um PC, então, aparentemente, esse não é um problema exclusivo do Mac ... Alguém teve sucesso em resolver esse problema de alguma forma?

@alonrbar Tenho o mesmo problema em um PC com Windows.

Na verdade, acabei de encontrar uma solução alternativa que funciona para mim.
A ideia geral é criar uma "grade de sombra" que oculta a grade original e rouba seu evento onScroll e então a usa para rolar manualmente a grade original, bem como qualquer outra grade necessária. Isso diminui um pouco o desempenho, mas mantém todas as grades bem sincronizadas, então você terá que considerar a troca.

O código se parece com isso;

import styled from '@emotion/styled';
import * as React from 'react';
import { VariableSizeGrid, VariableSizeGridProps } from 'react-window';
import { SizeUtils } from '../utils';

export interface SyncableGridProps extends VariableSizeGridProps {
    mainGridRef: React.Ref<VariableSizeGrid>;
    shadowGridRef: React.Ref<VariableSizeGrid>;
    hideVerticalScrollbar?: boolean;
}

export class SyncableGrid extends React.PureComponent<SyncableGridProps> {
    public render() {
        const { height, width } = this.props;
        const {
            onScroll,
            mainGridRef: mainGridRef1,
            shadowGridRef: shadowGridRef1,
            ...mainProps
        } = this.props;
        const {
            children,
            style,
            overscanRowsCount,
            overscanColumnsCount,
            overscanCount,
            useIsScrolling,
            onItemsRendered,
            mainGridRef: mainGridRef2,
            shadowGridRef: shadowGridRef2,
            innerRef,
            outerRef,
            ...shadowProps
        } = this.props;
        return (
            <SyncWrapper
                style={{
                    height,
                    width
                }}
            >
                <MainGrid
                    {...mainProps}
                    style={Object.assign({}, style, {
                        overflowY: 'scroll'
                    })}
                    ref={mainGridRef1}
                />
                <GridShadow
                    {...shadowProps}
                    style={{
                        position: 'absolute',
                        top: 0,
                        left: 0
                    }}
                    ref={shadowGridRef1}
                >
                    {() => null}
                </GridShadow>
            </SyncWrapper>
        );
    }
}

// ---------------- //
//      styles      //
// ---------------- //

const SyncWrapper = styled.div`
    position: relative;
    overflow: hidden;
`;

export interface MainGridProps extends VariableSizeGridProps {
    hideVerticalScrollbar?: boolean;
}

export const MainGrid = styled(VariableSizeGrid) <MainGridProps>`
    overflow-y: scroll;
    box-sizing: content-box;
    ${props => {
        if (!props.hideVerticalScrollbar)
            return '';
        const paddingDir = (props.theme.dir === 'rtl' ? 'padding-left' : 'padding-right');
        return `${paddingDir}: ${SizeUtils.scrollbarWidth}px;`;
    }}
`;

export const GridShadow = styled(MainGrid)`
    opacity: 0;
`;

Em seguida, em outro arquivo:

<SyncableGrid
    mainGridRef={this.firstGridMain}
    shadowGridRef={this.firstGridShadow}
    onScroll={this.handleFirstGridScroll}
    // other props omitted for bravity...
>
   // children omitted for bravity...
</SyncableGrid>
<SyncableGrid
    mainGridRef={this.secondGridMain}
    shadowGridRef={this.secondGridShadow}
    onScroll={this.handleSecondGridScroll}
    // other props omitted for bravity...
>
   // children omitted for bravity...
</SyncableGrid>

private handleFirstGridScroll = (e: GridOnScrollProps) => {
    const { scrollTop, scrollLeft } = e;

    // synchronize self
    if (this.firstGridMain.current) {
        this.firstGridMain.current.scrollTo({ scrollTop, scrollLeft });
    }

    // synchronize other grid
    if (this.secondGridMain.current) {
        this.secondGridMain.current.scrollTo({ scrollTop, scrollLeft });
        this.secondGridShadow.current.scrollTo({ scrollTop, scrollLeft });
    }
}

@alonrbar, isso é fascinante! Vou tentar isso em breve.

Parece que a grade de sombras vive acima da grade real, certo? Em caso afirmativo, clique em eventos na "grade real" não funcionará, não é? Suponho que você poderia fazer algo um pouco hackeado com eventos de clique + coords x / y e de alguma forma aplicar isso à grade principal.

Também @barbalex re: falha no Windows também

Pode ser uma bandeira do Chrome, na verdade. Veja se há algo em chrome: // flags. A Microsoft também pode ter implementado algo semelhante.

O problema certamente parece estar relacionado ao fato de a rolagem ser "suavizada" mais rapidamente do que o quadro de animação do navegador. Se você está preocupado é puramente uma coisa de desempenho / atraso, tente fazer uma implementação de sincronização de rolagem muito básica (onde a rolagem de um div define a posição de rolagem de outro) e veja se você obtém o mesmo problema. Talvez até mesmo faça isso em JS baunilha puro para eliminar React como o culpado

Ahhh @ranneyd você está certo, ele bloqueia eventos de clique ... Preciso pensar um pouco mais nisso ...

Acho que o recurso de rolagem encadeada em chrome: // flags tem uma grande influência: desligá-lo torna a rolagem com a roda do mouse quase tão suave quanto a rolagem com a barra de rolagem para mim.

2019-07-15_00h03_42

@alonrbar e quanto a isto:

Adicione um manipulador de rolagem à grade principal. Nele você faz e.preventDefault(); ou algo para evitar a rolagem real. Então você olha para o evento para descobrir o quanto ele TERIA rolado, o que isso faz para mover as outras coisas sincronizadas, mas então você usa isso para rolar manualmente esse mesmo elemento. Portanto, em vez de rolar A, e usar essa informação para rolar B, você intercepta o pergaminho em A, cancela-o e usa-o para rolar B e o próprio A. Isso funcionaria?

Não estou por um computador para testar. Bastante hacky, mas eu poderia trabalhar. pensamentos @bvaughn ?

@barbalex, você está certo, funciona para mim também, mas não posso permitir que todos os meus usuários liguem e desliguem os sinalizadores do Chrome 😔

@ranneyd Acabei de verificar e mexer um pouco no código-fonte ( link ), mas aparentemente você não pode desabilitar eventos de rolagem, a única coisa que você pode fazer é sequestrar todos os eventos de roda do mouse, toque e teclado que podem causar rolagem e evitá-los. O link (SO) tem um pequeno trecho de código para isso, mas isso o torna ainda mais hacky, então ainda estou pensando sobre isso ...

@alonrbar sim, pedir aos usuários para fazer isso não funcionará. Você _pode_ fornecer um link direto se _do_ quiser tentar: chrome: // flags / # disable -threaded-scrolling

@alonrbar eh isso não é bom, mas não é TERRÍVEL se estamos apenas aplicando na grade que já estamos hackeando juntos.

É apenas a roda de rolagem certo? As teclas de seta também fazem isso acontecer?

Este é um snippet da resposta aceita aqui: https://stackoverflow.com/questions/4770025/how-to-disable-scrolling-temporously

function disableScroll() {
  if (window.addEventListener) // older FF
      window.addEventListener('DOMMouseScroll', preventDefault, false);
  document.addEventListener('wheel', preventDefault, {passive: false}); // Disable scrolling in Chrome
  window.onwheel = preventDefault; // modern standard
  window.onmousewheel = document.onmousewheel = preventDefault; // older browsers, IE
  window.ontouchmove  = preventDefault; // mobile
  document.onkeydown  = preventDefaultForScrollKeys;
}

Como você pode ver, há vários eventos que você precisa tratar e considerar as possíveis consequências de fazê-lo.

No momento, como não posso investir mais tempo tentando resolver isso agora, decidi usar uma solução não virtual (que também não é perfeita, mas funciona melhor para meus casos de uso). Você pode encontrar o código que uso aqui e aqui (é parte da biblioteca que escrevi que envolve react-window ).

@alonrbar sim, eu concordo que a solução SO é muito ruim.

Acho sua solução, no entanto, muito interessante. Vou ver se consigo fazer minha própria tentativa e fazê-la funcionar com a mesa virtualizada.

@alonrbar, então fiz uma implementação não virtualizada que realmente usa posicionamento absoluto. A única coisa que rola é o recipiente externo. Na rolagem, ele atualiza os valores superior / esquerdo que são usados ​​para posicionar de forma absoluta os elementos internos. Portanto, nada de scrollTo ou scrollTop = ... , o que eu descobri que estava me causando muita dor. Além disso, as barras de rolagem estão SEMPRE do lado de fora de TODA a grade.

Essa coisa que eu fiz pode ter "cabeçalhos congelados" dinamicamente em todos os lados. É um exemplo MUITO aproximado / poc.

Obviamente, falta virtualização, o que é um problema sério. Infelizmente, não sei se posso envolver o Grid desta biblioteca nele, uma vez que essa biblioteca basicamente roda em rolagem e isso elimina a rolagem da grade interna. Não tenho certeza de qual é a próxima etapa.

https://codesandbox.io/embed/non-virtual-scroll-synced-table-ot467

Acho que a mesma lógica de virtualização que alimenta essa lib pode ser aplicada a isso.

@ranneyd implementação muito legal!

Eu gosto da composição da grade dinâmica :)

Mas o mais importante, a ideia de usar posicionamento absoluto em vez de rolagem pode ser a chave para resolver esse problema. Ao introduzir outro div, você desconectou o evento onscroll da rolagem de conteúdo real, o que acho que abre novas possibilidades.

Se você pudesse se conectar ao método react-window de render , você poderia substituir as linhas 459 e continuar com sua implementação. Então, você será capaz de conectar o evento onscroll e desabilitar seu efeito quando necessário (as barras de rolagem continuarão sempre se movendo, mas você pode controlar o conteúdo para evitar que ele mude).

@alonrbar, então

Se você estiver interessado na grade que fiz, posso postá-la em algum lugar. Se você quiser pegá-lo e conectá-lo a esta biblioteca, fique à vontade 😏

@ranneyd sim, gostaria de ver a solução de virtualização que você criou. Não sei se vou usar, mas com certeza seria interessante :)

Além disso, eu estava tentando usar seu código hoje para implementar as alterações no método de renderização como sugeri, mas depois de reler seu código, percebi que você não desconectou a rolagem da maneira que pensei. O que quero dizer com isso é que as barras de rolagem e o evento onscroll ainda são inseparáveis ​​da rolagem de conteúdo real. Eu levei sua ideia um passo adiante e implementei um pergaminho verdadeiramente desconectado:

https://codesandbox.io/embed/absolute-position-scrolling-1u7vj

Se você olhar o código, verá que, se removermos as propriedades top e left do componente Content , o conteúdo não será rolado, mesmo que as barras de rolagem fazer . Também verifiquei que desta vez os eventos do mouse não estão bloqueados. 😂
Acredito que agora seja possível usar esta implementação para ignorar o evento onscroll e sincronizar várias grades manualmente. Mas é claro que isso requer um pouco mais de trabalho, então terá que esperar pela próxima vez ...

Esta discussão me deixou curioso, então eu configurei um projeto para comparar duas janelas de reação sincronizadas por rolagem Grid s com uma virtualização reativa MultiGrid , para que eu possa ter uma noção de como o desempenho se compara :
https://github.com/bvaughn/react-window-vs-react-virtualized-synced-grids

Tentei tornar cada uso o mais semelhante possível. Algumas observações iniciais:

  • react-window parece visivelmente mais lento no modo DEV, mas parece um pouco mais rápido / responsivo em uma construção de produção. (É difícil descartar preconceitos da minha parte aqui. A diferença na produção é menor.)
  • react-window também faz muito mais commits, uma vez que o evento scroll primeiro atualiza a grade ativa, então a grade passiva por meio de uma atualização em cascata. Fiz um patch para Grid para suportar a passagem de um manipulador de eventos de rolagem "nativo" (React) (a ser chamado na atualização em lote do React) em vez do atual onScroll (que é chamado durante o commit Estágio). Esta parece ser uma mudança muito promissora quando testei localmente, uma vez que evita renderizações em cascata separadas, então talvez eu apenas altere o tempo padrão de onScroll .

@bvaughn Eu tentei fazer o código de efeito de rolagem mais básico, vanilla, com divA -> onScroll -> setState -> ref.scrollTop e ainda não consegui contornar essa coisa de rolagem encadeada cromada. Admito que não evitei o estado de reação e configurei ref.scrollTop dentro do manipulador onScroll, mas além disso, não consigo pensar em uma maneira mais básica de fazer isso. Se você não consegue onScroll -> scrollTop rápido o suficiente no menor número possível de etapas, como você conserta isso? Estou totalmente perdendo alguma coisa? Parece que as grades não precisam se mover com base na rolagem (ou na configuração de scrollTop).

O objetivo é sempre que o JavaScript seja o mais rápido possível, para que ele acompanhe a rolagem do gerenciamento do thread.

Admito que não evitei o estado de reação e configurei ref.scrollTop no manipulador onScroll

Só para ficar claro, não é isso que meu repo está fazendo. Estou apenas configurando o deslocamento de rolagem na grade passiva no mesmo manipulador de eventos (envolvido por React) da grade ativa, então o React agrupa suas atualizações em um único render + commit. Isso provavelmente não faz muita diferença para ser honesto.

@ranneyd e @bvaughn compartilhando com vocês minhas observações até agora:

  1. Muitas implementações funcionam bem no navegador de desktop, mas meu caso de uso principal é, na verdade, em dispositivos móveis e vejo enormes diferenças de desempenho.

  2. Sincronizar duas grades não virtuais simples funciona muito bem (não 100% perfeito, mas próximo o suficiente, mesmo em dispositivos móveis). Esta é minha implementação ingênua, mas funcional, e um caso de teste para vê-la em ação ( yarn storybook ).

  3. Usar uma rolagem "controlada" (como sugeri neste comentário ) mantém as duas grades 100% sincronizadas, mas é muito lento no desktop e totalmente lento no celular. Este é o meu raciocínio (muito difícil ...): https://github.com/alonrbar/react-window/commit/c39ce7006dbd590d9c640e37f8a1e78826e4688e

    Mesmo assim, estou tentado a ver se a estratégia "controlada" pode funcionar de alguma forma para resolver esse problema em uma única lista virtual.

  4. Eu estava pensando, como vocês dois já sugeriram, que talvez mover o _callPropsCallbacks para o manipulador _onScroll diretamente pode ajudar a reduzir o atraso de sincronização ao mínimo. EDITAR - experimentei agora, não estou ajudando muito 😞

  5. De qualquer forma, uma boa ideia IMHO é separar a lógica de virtualização do resto do componente (talvez até mesmo um gancho?) Para que as coisas de renderização e onscroll possam ser tratadas separadamente, comparadas facilmente e até trocadas em tempo de execução com base no adereços de componente. Também será possível exportar essa lógica e permitir que os usuários implementem componentes personalizados (como a solução @ranneyd neste comentário ) com base na mesma lógica.

Pensamentos?

@alonrbar minha solução, que faz o posicionamento absoluto, então nenhuma das grades é a única fazendo a rolagem, funciona muito bem até cerca de 300x300, onde fica um pouco lento (observe que permanece em sincronia 100% do tempo, a rolagem apenas fica um pouco lento). Em tamanhos maiores, acho que ele está apenas processando / mapeando uma grande matriz. Acho que existem várias otimizações
Eu posso fazer, no entanto, e não estou totalmente convencido de que isso tenha algo a ver com a sincronização de rolagem, mas com minha implementação de virtualização bastante simples. Posso fazer mais caching, por exemplo, e posso calcular se uma célula deve ser virtualizada ou não antes para evitar a chamada da função render (e então cache melhor, talvez).

Não testei nada no celular. Fornecerei um código para você experimentar em breve.

Eu realmente quero dar uma olhada no teste @bvaughn . Ele diz que ligar diretamente no pergaminho nativo corrige, você diz que não. Eu quero ver por mim mesmo.

Quanto a puxar a lógica de virtualização para um gancho ou uma função independente, fica bem complicado porque a lógica está intrinsecamente conectada à visualização. Também parece que muitos ajustes de desempenho envolvem armazenamento em cache e coisas de memoização que podem ser difíceis de encapsular em um gancho ou função, ou pelo menos até o ponto em que obtém o mesmo desempenho. Mas vou olhar o que tenho e ver o quanto da lógica posso extrair.

PS:
Uma coisa que acabei de pensar que provavelmente não funcionaria é fazer uma coisa do tipo denunciar + transições css. Se executarmos apenas um evento de rolagem a cada 100 ms ou algo assim e animarmos o movimento, pode parecer melhor. Também pode parecer substancialmente menos responsivo. É mais ou menos como fizeram o World of Warcraft (se houver atraso ou alta latência, eles farão um personagem se mover em linha reta e corrigirão assim que obtiverem a informação de para onde realmente foram).

Eu realmente quero dar uma olhada no teste @bvaughn . Ele diz que ligar diretamente no pergaminho nativo corrige, você diz que não. Eu quero ver por mim mesmo.

Eu não disse isso: smile: Eu apenas disse que evita um render desnecessário extra e uma mutação DOM. Quanto impacto isso tem no desempenho real não está claro para mim. No entanto, parece uma mudança geral positiva.

@alonrbar @bvaughn Eu preciso realmente documentar meu código, mas aqui está a versão mais recente:

https://github.com/ranneyd/synced-table

@alonrbar @bvaughn então aqui está uma coisa divertida que acabei de descobrir:

Minha solução NÃO funciona no Chrome 75 em uma tela de macbook. Quando meus colegas de trabalho não atualizaram o cromo, funcionou. Se eles usarem um monitor externo, funciona. Na tela do laptop, ele fica lento.
😩

Hmm ... existem algumas diferenças com monitores externos, com taxa de atualização ou escala. Quando você diz que "não funciona", o que quer dizer especificamente?

Foi mal. Quero dizer, a rolagem não está mais sincronizada. Se você clonar meu repo e executá-lo, poderá comparar o monitor externo com a tela do laptop. No monitor, eles estão perfeitamente sincronizados. Na tela do laptop, os cabeçalhos piscam (não atualize com a mesma velocidade do elemento de rolagem).

Na verdade, eu desisti e estou tentando com position: sticky . Está realmente funcionando. O suporte do navegador não é 100%, mas na verdade é muito melhor do que há um ano.

Existe essa lib que é um polyfill não polyfill que pode funcionar, mas os navegadores que visamos oferecem suporte ao recurso.
https://github.com/dollarshaveclub/stickybits

@alonrbar @bvaughn aqui está a versão mais recente. Ele usa position: sticky . Eu explico um pouco no readme. O código ainda precisa ser documentado.

https://github.com/ranneyd/sticky-table

Minha solução NÃO funciona no Chrome 75 em uma tela de macbook. Quando meus colegas de trabalho não atualizaram o cromo, funcionou. Se eles usarem um monitor externo, funciona. Na tela do laptop, ele fica lento.
😩

@ranneyd Ocorreu-me hoje que isso pode estar relacionado à taxa de quadros. A taxa de quadros para um monitor externo pode ser, por exemplo, 30 fps, caso em que acho que o navegador pode despachar menos eventos de "rolagem" (uma vez que acho que a maioria dos navegadores despacha apenas um por quadro / pintura). Talvez seja por isso que o atraso é mais perceptível para você em um monitor externo?

@bvaughn acho que tem que ser isso. O SO DIZ que está enviando 60Hz e ACHO que as especificações do monitor dizem 60Hz, mas não me surpreenderia se alguém estivesse mentindo 😂

@ranneyd Lamento dizer que não estou muito disponível no momento, então não pude dar uma boa olhada em sua solução, mas, olhando rapidamente, percebi que parece muito legal e que você criou um useVirtual gancho e separou elegantemente a lógica da renderização.
Eu acho que seria ótimo se você pudesse de alguma forma criar uma solicitação de pull para react-window , usá-la lá e talvez expor um método renderTable de algum tipo onde você pudesse conectar sua lógica de renderização. Desta forma, sua biblioteca poderia apenas envolver a janela de reação em vez de substituí-la. Eu acredito que seria preferível se pudéssemos aproveitar esta abordagem e resolver o problema dentro de react-window que já está praticamente testado e amplamente difundido.

Por outro lado, acho que se scrollTo usar a manipulação DOM direta (talvez além de definir o estado, mas em qualquer caso antes de fazer isso), o resultado da sincronização de duas tabelas ficará mais suave. Se não me engano, @bvaughn também estava sugerindo algo nesse sentido.

@ranneyd sua solução sticky table parece realmente ótima e eu ficaria muito feliz em poder usá-la. Você consideraria lançar uma api utilizável para ele em npm ?

@bvaughn @ranneyd

Então, depois de algum tempo, voltei recentemente a esse assunto. Usando código e ideias de suas bibliotecas, bem como de recyclerlistview e react-virtual-grid , finalmente consegui obter o comportamento que queria, ou seja, uma grade de alto desempenho com linhas e colunas fixas que funciona bem tanto em desktop quanto em dispositivos móveis dispositivos (rolagem suave sem células vazias / brancas).

O TLDR disso é que eu uso a reciclagem junto com o posicionamento fixo e a parte mais interessante do código pode ser encontrada aqui neste método .

Os créditos e mais sobre a motivação para escrever outra solução estão aqui . Obrigado!

Vou deixar um link para meu exemplo de trabalho do Code Sandbox aqui, caso alguém mais se depare com esse problema no futuro.
https://codesandbox.io/s/y3pyp85zm1

Muito bom, até que você role completamente para a direita: então os cabeçalhos ficam desalinhados com o conteúdo (pela largura da barra de rolagem vertical de rolagem).
Por acaso você teria encontrado uma solução para isso?
Obrigado

Vou deixar um link para meu exemplo de trabalho do Code Sandbox aqui, caso alguém mais se depare com esse problema no futuro.
https://codesandbox.io/s/y3pyp85zm1

Muito bom, até que você role completamente para a direita: então os cabeçalhos ficam desalinhados com o conteúdo (pela largura da barra de rolagem vertical de rolagem).
Por acaso você teria encontrado uma solução para isso?
Obrigado

Divulgação completa: Na verdade, fiz minha própria versão do zero. Ele tem sua própria virtualização baseada fortemente nesta lib. A razão pela qual fiz isso foi porque há um problema nos MacBooks e com alguns sinalizadores do Chrome em que a animação de rolagem acontece com um tempo diferente do JS. Usar ScrollSync exigia muitas chamadas de função passadas e era muito lento. Basicamente, tive que refazer a biblioteca com cabeçalhos fixos em seu núcleo (não usei position: sticky como disse anteriormente neste tópico. Preciso fazer o upload do que tenho agora em algum ponto).

Dito isso, a maneira de contornar esse problema é overflow: scroll para forçar uma barra de rolagem e, em seguida, adicionar esse preenchimento à última célula, ou determino dinamicamente se há uma barra de rolagem (e qual é sua largura) usando um ref e colocando um div invisível dentro dele, medindo o tamanho da barra de rolagem e excluindo o nó DOM.

Eu usei overflow-y: overlay que faz a sobreposição da barra de rolagem. Infelizmente, só funciona para navegadores de webkit.

Ok, obrigado pela informação. Realmente parece que os cabeçalhos fixos são uma necessidade tão comum, que apenas adicionar a opção da primeira linha fixa à janela de reação faria com que ela substituísse o virtualizado de reação em muitos outros casos. Você não acha?

@ranneyd obrigado novamente, segui sua abordagem e a sincronização de rolagem funciona bem.

O estouro oculto nas barras de rolagem causa desalinhamento próximo ao final da grade:

gridalignement

Alguma sugestão sobre como consertar isto?

Exemplo de CodeSandbox

desde já, obrigado

Atrasado para a festa, mas se você simplesmente adicionar uma linha à referência esquerda e colocar seu estouro em oculto, você basicamente resolve o pb. (Eu fiz isso também para não ter que sincronizar o leftRef -> main Grid

  const headerRef = React.useRef();
  const leftRef   = React.useRef();

  return <Box classes={{
            root:classes.tableContainer
          }}>
      <AutoSizer>
        {({ height, width }) => (<>
        {/*---------------- LA TABLE -------------*/}
          <Grid
            columnCount={1000}
            columnWidth={100}
            height={height}
            rowCount={1000}
            rowHeight={35}
            width={width}
            onScroll={({ scrollLeft, scrollTop }) => {
              if (leftRef.current) {
                leftRef.current.scrollTo({ scrollTop });
              }
              if (headerRef.current) {
                headerRef.current.scrollTo({ scrollLeft });
              }
            }}
          >
            {({ columnIndex, rowIndex, style }) => (
              <Box style={style} classes={{root:classes.cell}}>
                Item {rowIndex},{columnIndex}
              </Box>
            )}
          </Grid>
          {/*---------------- HEADER -------------*/}
          <Grid
            ref={headerRef}
            outerElementType={React.forwardRef((props, ref) => (
              <div ref={ref}  {...props} style={{...props.style,position:"absolute",overflow:"hidden",top:0,right:0,left:150}} />
            ))}
            columnCount={1001}  /*columns count +1 for scroll problems*/
            columnWidth={100}
            height={60}
            rowCount={1}
            rowHeight={60}
            width={width}
          >
            {({ columnIndex, rowIndex, style }) => (
              <Box style={style} classes={{root:classes.headerCell}}>
                Header {rowIndex},{columnIndex}
              </Box>
            )}
          </Grid>  
          {/*---------------- LEFT COL -------------*/}
          <Grid
            ref={leftRef}
            outerElementType={React.forwardRef((props, ref) => (
              <div ref={ref}  {...props} style={{...props.style,position:"absolute",overflow:"hidden",top:60,left:0}} />
            ))}
            columnCount={1}
            columnWidth={150}
            height={height}
            rowCount={251} /** add 1 for scroll problems at the end */
            rowHeight={140}
            width={150}
          >
            {({ columnIndex, rowIndex, style }) => (
              <Box style={style} classes={{root:classes.headerCell}}>
                Left {rowIndex},{columnIndex}
              </Box>
            )}
          </Grid>  
        </>)}
      </AutoSizer>
    </Box>
Esta página foi útil?
0 / 5 - 0 avaliações