React-window: Compatibilité avec ScrollSync

Créé le 8 nov. 2018  ·  52Commentaires  ·  Source: bvaughn/react-window

J'ai un cas d'utilisation où je dois créer une grille qui a des en-têtes fixes qui restent épinglés en haut. Dans React-virtualized, cela pourrait être accompli en créant deux composants de grille (un pour les en-têtes, un pour le corps de la grille) et en synchronisant la position de défilement afin que le défilement horizontal de la grille d'en-tête soit synchronisé avec la grille principale.

Je sais que vous essayez de réduire le poids de la fenêtre de réaction, à la fois en termes de taille de paquet et de complexité conceptuelle, en faveur de la création de fonctionnalités supplémentaires sous forme de packages séparés. C'est une direction avec laquelle je suis d'accord. Je pense que le composant ScrollSync de react-virtualized pourrait être extrait ou adapté pour fonctionner avec react-window. Cependant, pour ce faire, react-window devrait accepter les accessoires pour scrollLeft et scrollTop afin que le décalage de défilement de la grille d'en-tête puisse être directement géré.

Est-ce un cas d'utilisation que vous seriez prêt à soutenir ? Sinon, avez-vous des conseils sur la direction que je devrais prendre pour mettre cela en œuvre moi-même?

Merci pour votre travail sur cette bibliothèque. En tant qu'utilisateur de react-virtualized depuis quelques années, j'apprécie la simplicité de démarrage de react-window et à quel point cela a été performant pour moi sans trop d'intervention manuelle.

Commentaire le plus utile

Cela a tout son sens. Merci pour la suggestion! Je l'ai fait fonctionner, et c'est en fait assez facile à configurer. Si facile que cela ne justifie certainement pas un package autonome. Peut-être un exemple dans la doc, mais c'est votre appel je suppose. Je vais laisser un lien vers mon exemple de travail Code Sandbox ici au cas où quelqu'un d'autre tomberait sur ce problème à l'avenir.

https://codesandbox.io/s/y3pyp85zm1

TLDR - placez une référence sur la grille d'en-tête et placez-la sur la grille du corps.

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

Tous les 52 commentaires

Tout d'abord, merci pour les mots gentils et les commentaires positifs. Je suis heureux d'apprendre que la fenêtre de réaction a bien fonctionné pour vous jusqu'à présent !

Je suis d'accord qu'un composant comme ScrollSync pourrait être publié en tant que package autonome qui dépend de la fenêtre de réaction, mais je ne voudrais pas l'ajouter à ce projet car il n'est pas essentiel au fenêtrage.

En ce qui concerne votre question spécifique sur les accessoires de défilement, ce n'est pas un changement que je serais prêt à apporter au projet car après l'avoir utilisé avec react-virtualized, j'ai réalisé qu'il présentait de sérieux inconvénients. En fait, j'en ai parlé sur le blog React si vous êtes curieux :

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

Vous pouvez utiliser l'API de défilement impératif que react-window propose pour obtenir un comportement de synchronisation similaire. Vous n'auriez qu'à appeler ces méthodes à partir des cycles de vie des commits plutôt qu'en passant des props.

J'espère que cela a du sens, mais n'hésitez pas à poser des questions de suivi si ce n'est pas le cas !

Cela a tout son sens. Merci pour la suggestion! Je l'ai fait fonctionner, et c'est en fait assez facile à configurer. Si facile que cela ne justifie certainement pas un package autonome. Peut-être un exemple dans la doc, mais c'est votre appel je suppose. Je vais laisser un lien vers mon exemple de travail Code Sandbox ici au cas où quelqu'un d'autre tomberait sur ce problème à l'avenir.

https://codesandbox.io/s/y3pyp85zm1

TLDR - placez une référence sur la grille d'en-tête et placez-la sur la grille du corps.

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

Merci pour le lien! C'est très réfléchi.

Le jeu. 8 novembre 2018, 13:09 Reagan Keeler < [email protected] a écrit :

Cela a tout son sens. Merci pour la suggestion! je l'ai fait fonctionner,
et c'est en fait assez facile à mettre en place. Si facile que ce n'est certainement pas le cas
justifier un package autonome. Peut-être un exemple dans la doc, mais c'est votre
appeler je suppose. Je vais laisser un lien vers mon exemple de travail Code Sandbox ici
au cas où quelqu'un d'autre tomberait sur ce problème à l'avenir.

https://codesandbox.io/s/y3pyp85zm1

TLDR - placez une référence sur la grille d'en-tête et placez-la sur la grille du corps.

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

-
Vous recevez ceci parce que vous avez modifié l'état ouvert/fermé.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/bvaughn/react-window/issues/86#issuecomment-437156749 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AABznTUunzEIs6bVQfVsz7T21L2-Pkkoks5utJ17gaJpZM4YVMd7
.

Pour tous ceux qui sont tombés sur cela, j'avais une super grille gelée en haut/à droite/en bas/à gauche synchronisée avec le défilement dans react-virtualized que je viens de migrer vers cette lib. Je dirai que la solution ci-dessus est au moins aussi performante sinon meilleure, et sensiblement moins douloureuse que l'utilisation de ScrollSync dans l'autre bibliothèque.

Il se marie également assez bien avec le nouveau crochet de réaction useRef (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 });
    }
  }}
  ...
/>

Joli! Merci pour le partage @ranneyd !

Il se marie également assez bien avec le nouveau crochet de réaction useRef (https://reactjs.org/docs/hooks-reference.html#useref)

Merci @ranneyd

Serait-il possible de partager un exemple de travail?

@ranneyd merci encore, j'ai suivi votre approche et la synchronisation du défilement fonctionne bien.

Le débordement masqué sur les barres de défilement provoque un désalignement près de la fin de la grille :

gridalignement

Des suggestions sur la façon de résoudre ce problème ?

Exemple de CodeSandbox

Merci d'avance

@carlosagsmendes c'est à cause de la barre de défilement. Sur la gauche, la hauteur correspond à la hauteur moins la taille de la barre de défilement. Pour obtenir une cohérence entre les appareils, je code manuellement en dur la taille de la barre de défilement avec CSS. Si votre contenu est si dynamique qu'il peut y avoir ou non une barre de défilement, faites une chose comme " num items * size items < width (vous devriez avoir toutes ces valeurs juste là) ", puis modifiez la hauteur en fonction de cela.

Pour obtenir une cohérence entre les appareils, je code manuellement en dur la taille de la barre de défilement avec CSS.

FWIW le package dom-helpers a une fonction pratique scrollbarSize qui vous indique quelle est la largeur sur le périphérique actuel. Peut-être mieux que le codage en dur.

@bvaughn oui ! J'ai oublié de mentionner cela. Cependant, cela n'a pas fonctionné pour nous. Je pense que le problème était que nous faisions déjà des barres de défilement codées en dur et que cela le rendait confus

Merci. Je vais essayer!

Cela fonctionne-t-il avec la VariableSizeList horizontale ? J'ai essayé mais mon navigateur se bloque.

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

Cela fonctionne bien avec VariableSizeGrid mais mon en-tête est un peu en retard sur le défilement.

@ajaymore donc le retard de l'en-tête: la synchronisation du défilement a des difficultés lorsque vous utilisez la molette de défilement sur un Mac. MacOSX a cette fonction de défilement naturel intégrée où il interpole le défilement pour le rendre "plus fluide". La fréquence de rafraîchissement est en fait plus rapide que la fréquence d'images de l'animation dans Chrome. Ainsi, l'élément que vous faites défiler va s'animer avant que V8/ScrollSync n'ait le temps de mettre à jour le DOM. Il s'agit d'une limitation technique pour laquelle je n'ai pas vu de solution.

Fait amusant : si vous utilisez manuellement la barre de défilement dans le navigateur (comme faire défiler à l'ancienne en faisant glisser la petite chose), cela fonctionne parfaitement. L'interpolation est intégrée dans la molette de défilement/le pavé tactile

@ajaymore donc le retard de l'en-tête: la synchronisation du défilement a des difficultés lorsque vous utilisez la molette de défilement sur un Mac. MacOSX a cette fonction de défilement naturel intégrée où il interpole le défilement pour le rendre "plus fluide". La fréquence de rafraîchissement est en fait plus rapide que la fréquence d'images de l'animation dans Chrome. Ainsi, l'élément que vous faites défiler va s'animer avant que V8/ScrollSync n'ait le temps de mettre à jour le DOM. Il s'agit d'une limitation technique pour laquelle je n'ai pas vu de solution.

Fait amusant : si vous utilisez manuellement la barre de défilement dans le navigateur (comme faire défiler à l'ancienne en faisant glisser la petite chose), cela fonctionne parfaitement. L'interpolation est intégrée dans la molette de défilement/le pavé tactile

@ranneyd Merci pour cette réponse si rapide. Je suis d'accord, c'est une limitation. Cela fonctionnera très bien sur la majorité des appareils, donc ce n'est pas un gros problème alors.

@bvaughn avez-vous déjà joué avec position: sticky ? Je ne l'ai pas regardé depuis un moment et je sais que la prise en charge du navigateur est limitée, mais je me demande s'il existe un moyen de l'exploiter dans les navigateurs où il est pris en charge...

J'ai le même problème que @ajaymore , c'est-à-dire que la synchronisation de deux composants est lente (comme indiqué, cela fonctionne bien sur le défilement manuel). Je teste avec Chrome sur un PC, donc apparemment ce n'est pas un problème uniquement sur Mac... Quelqu'un a-t-il réussi à contourner ce problème d'une manière ou d'une autre ?

@alonrbar J'ai le même problème sur un PC Windows.

En fait, je viens de trouver une solution de contournement qui fonctionne pour moi.
L'idée générale est de créer une "grille d'ombre" qui se cache dans la grille d'origine et vole son événement onScroll, puis l'utilise pour faire défiler manuellement la grille d'origine ainsi que toute autre grille nécessaire. Cela ralentit un peu les performances mais garde toutes les grilles bien synchronisées, vous devrez donc considérer le compromis.

Le code ressemble à ceci ;

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;
`;

Puis dans un autre fichier :

<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 c'est fascinant ! Je vais essayer ça bientôt.

On dirait que la grille d'ombre vit au-dessus de la vraie grille, non ? Si c'est le cas, cliquez sur les événements sur la « vraie grille » ne fonctionneront pas, n'est-ce pas ? Je suppose que vous pourriez faire quelque chose d'un peu hacky avec des événements de clic + des coordonnées x/y et l'appliquer d'une manière ou d'une autre à la grille principale.

Aussi @barbalex re: échoue aussi dans Windows

Il pourrait s'agir d'un drapeau chromé en fait. Voyez s'il y a quelque chose dans chrome://flags. Microsoft a peut-être également implémenté quelque chose de similaire.

Le problème semble certainement être lié au fait que le défilement est "lissé" plus rapidement que le cadre d'animation du navigateur. Si vous êtes concerné, c'est purement une question de performance/lag, essayez de faire une implémentation de synchronisation de défilement très basique (où le défilement d'un div définit la position de défilement d'un autre) et voyez si vous rencontrez le même problème. Peut-être même le faire en pure vanille JS pour éliminer React comme coupable

Ahhh @ranneyd vous avez raison, cela bloque les événements de clic... Besoin de réfléchir davantage à cela...

Je pense que la fonction de défilement Threaded dans chrome://flags a une grande influence : la désactiver rend le défilement avec la molette de la souris aussi fluide que le défilement avec la barre de défilement pour moi.

2019-07-15_00h03_42

@alonrbar qu'en est-il de ceci:

Ajoutez un gestionnaire de défilement à la grille principale. Vous y faites e.preventDefault(); ou quelque chose pour empêcher le défilement réel. Ensuite, vous regardez l'événement pour déterminer combien il AURAIT défilé, ce que cela fait pour déplacer les autres éléments synchronisés, mais vous l'utilisez ensuite pour faire défiler manuellement ce même élément. Ainsi, au lieu de faire défiler A, puis d'utiliser ces informations pour faire défiler B, vous interceptez le défilement sur A, l'annulez, puis l'utilisez pour faire défiler B et A lui-même. Cela fonctionnerait-il ?

Je ne suis pas près d'un ordinateur pour tester. Assez hacky mais je pourrais travailler. @bvaughn pensées?

@barbalex tu as raison, ça marche pour moi aussi mais je ne peux pas faire en sorte que tous mes utilisateurs activent et désactivent les drapeaux chrome 😔

@ranneyd Je viens de l'examiner et de jouer un peu avec le code source ( lien ) mais apparemment, vous ne pouvez pas désactiver les événements de défilement, la seule chose que vous pouvez faire est de détourner tous les

@alonrbar oui, demander aux utilisateurs de le faire ne fonctionnera pas. Vous _pouvez_ leur donner un lien direct si vous _voulez_ essayer : chrome://flags/#disable -threaded-scrolling

@alonrbar eh ce n'est pas bon mais ce n'est pas TERRIBLE si nous l'appliquons simplement à la grille que nous allons déjà pirater ensemble.

C'est juste la molette de défilement, n'est-ce pas ? Les touches fléchées permettent-elles également de le faire ?

Ceci est un extrait de la réponse acceptée ici : https://stackoverflow.com/questions/4770025/how-to-disable-scrolling-temporarily

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;
}

Comme vous pouvez le voir, il y a plusieurs événements que vous devez gérer et considérer les conséquences possibles de le faire.

Pour le moment, comme je ne peux pas investir plus de temps à essayer de résoudre ce problème pour le moment, j'ai décidé d'utiliser une solution non virtuelle (qui n'est pas parfaite non plus mais fonctionne mieux pour mes cas d'utilisation). Vous pouvez trouver le code que j'utilise ici et ici (il fait partie de la bibliothèque que j'ai écrite et qui enveloppe react-window ).

@alonrbar ouais je suis d'accord que la solution SO est assez minable.

Je trouve votre solution, cependant, très intéressante. Je vais voir si je peux prendre ma propre chance et le faire fonctionner avec la table virtualisée.

@alonrbar j'ai donc fait une implémentation non virtualisée qui utilise en fait le positionnement absolu. La seule chose qui défile est le conteneur extérieur. Lors du défilement, il met à jour les valeurs en haut/à gauche qui sont utilisées pour positionner de manière absolue les éléments internes. Donc pas de scrollTo ou scrollTop = ... , ce que j'ai trouvé me causait beaucoup de chagrin. En plus de cela, les barres de défilement sont TOUJOURS à l'extérieur de la grille ENTIÈRE.

Cette chose que j'ai faite peut avoir dynamiquement des "en-têtes gelés" de tous les côtés. C'est un exemple/poc TRÈS approximatif.

De toute évidence, il manque de virtualisation, ce qui est un grave problème. Malheureusement, je ne sais pas si je peux y encapsuler Grid de cette bibliothèque, car cette bibliothèque fonctionne essentiellement sur le défilement et cela élimine le défilement de la grille intérieure. Je ne sais pas quelle est la prochaine étape.

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

Je pense que la même logique de virtualisation qui alimente cette bibliothèque peut être appliquée à cela.

@ranneyd implémentation très cool!

J'aime la composition dynamique de la grille :)

Mais surtout, l'idée d'utiliser le positionnement absolu au lieu du défilement peut être la clé pour résoudre ce problème. En introduisant un autre div, vous avez déconnecté l'événement onscroll du défilement du contenu réel, ce qui, je pense, ouvre de nouvelles possibilités.

Si vous pouvez accrocher dans react-window « s render méthode que vous pourriez être en mesure de remplacer les lignes 459 et sur votre mise en œuvre. Ensuite, vous pourrez accrocher l'événement onscroll et désactiver son effet si nécessaire (les barres de défilement bougeront toujours mais vous pouvez contrôler le contenu pour l'empêcher de changer).

@alonrbar , j'ai donc

Si vous êtes intéressé par la grille que j'ai faite, je peux la poster quelque part. Si vous voulez le prendre et le brancher avec cette lib alors soyez mon invité

@ranneyd oui, j'aimerais voir la solution de virtualisation que vous avez

De plus, j'essayais d'utiliser votre code aujourd'hui pour implémenter les modifications apportées à la méthode de rendu comme je l'ai suggéré, mais après avoir relu votre code, j'ai remarqué que vous n'avez pas réellement déconnecté le défilement comme je le pensais. Ce que je veux dire par là, c'est que les barres de défilement et l'événement onscroll sont toujours inséparables du défilement du contenu réel. J'ai poussé votre idée un peu plus loin et j'ai mis en place un parchemin vraiment déconnecté :

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

Si vous regardez le code, vous verrez que si nous supprimons les propriétés top et left du composant Content , le contenu ne défile pas même si les barres de défilement faire . J'ai également vérifié que cette fois les événements de souris ne sont pas bloqués. ??
Je pense qu'il est maintenant possible d'utiliser cette implémentation pour ignorer l'événement onscroll et synchroniser manuellement plusieurs grilles. Mais cela demande bien sûr un peu plus de travail donc il faudra attendre la prochaine fois...

Cette discussion m'a curieux, si la configuration je un projet pour comparer deux défilement synced réagir fenêtre Grid s avec un RÉAGIR virtualisé MultiGrid , afin que je puisse avoir une idée de la façon dont perf compare :
https://github.com/bvaughn/react-window-vs-react-virtualized-synced-grids

J'ai essayé de rendre chaque utilisation aussi similaire que possible. Quelques premiers constats :

  • la fenêtre de réaction semble sensiblement plus lente en mode DEV, mais semble un peu plus rapide/plus réactive dans une version de production. (Il est difficile d'exclure un parti pris de ma part ici. La différence de production est plus petite.)
  • react-window fait également beaucoup plus de commits, puisque l'événement scroll met d'abord à jour la grille active, puis la grille passive via une mise à jour en cascade. J'ai fait un correctif à Grid pour prendre en charge le passage d'un gestionnaire d'événements de défilement "natif" (React) (à appeler dans la mise à jour par lots de React) plutôt que le onScroll actuel (qui est appelé pendant la validation phase). Cela semble être un changement assez prometteur lorsque je l'ai testé localement, car cela évite des rendus en cascade séparés, alors peut-être que je vais juste changer le timing onScroll par défaut.

@bvaughn J'ai essayé de créer le code d'effet de défilement vanille le plus basique, avec divA -> onScroll -> setState -> ref.scrollTop et il ne pouvait toujours pas contourner ce défilement fileté chromé. Certes, je n'ai pas contourné l'état de réaction et défini ref.scrollTop dans le gestionnaire onScroll, mais à part cela, je ne peux pas penser à un moyen plus simple de le faire. Si vous ne pouvez pas accéder à onScroll -> scrollTop assez rapidement en le moins d'étapes possible, comment y remédier ? Est-ce que je rate totalement quelque chose ? Il semble que les grilles n'aient pas besoin de se déplacer en fonction du défilement (ou du réglage de scrollTop).

Le but est toujours que JavaScript soit le plus rapide possible afin qu'il suive le fil qui gère le défilement.

Certes, je n'ai pas contourné l'état de réaction et j'ai défini ref.scrollTop dans le gestionnaire onScroll

Juste pour être clair, ce n'est pas ce que fait mon repo non plus. Je définis simplement le décalage de défilement dans la grille passive dans le même gestionnaire d'événements (encapsulé par React) que la grille active, donc React regroupe ses mises à jour dans un seul rendu + validation. Cela ne fait probablement pas beaucoup de différence pour être honnête.

@ranneyd et @bvaughn partagent avec vous mes observations jusqu'à présent :

  1. De nombreuses implémentations fonctionnent assez bien sur le navigateur de bureau, mais mon cas d'utilisation principal est en fait sur les appareils mobiles et là, je constate d'énormes différences de performances.

  2. La synchronisation de deux grilles simples non virtuelles fonctionne plutôt bien (pas parfaite à 100% mais assez proche, même sur mobile). Ceci est mon implémentation naïve, mais fonctionnelle, et un cas de test pour le voir en action ( yarn storybook ).

  3. L'utilisation d'un défilement "contrôlé" (comme je l'ai suggéré dans ce commentaire ) permet de synchroniser les deux grilles à 100%, mais est assez lent sur le bureau et totalement trop lent sur le mobile. C'est ma chance (très rude...) : https://github.com/alonrbar/react-window/commit/c39ce7006dbd590d9c640e37f8a1e78826e4688e

    Même ainsi, je suis tenté de voir si la stratégie "contrôlée" peut en quelque sorte résoudre ce problème sur une seule liste virtuelle.

  4. Je pensais, comme vous l'aviez déjà suggéré, que déplacer directement le _callPropsCallbacks vers le gestionnaire _onScroll pourrait aider à réduire le délai de synchronisation au minimum. EDIT - je viens d'essayer maintenant, ça ne m'aide pas vraiment

  5. De toute façon, vous l'abordez, une bonne idée à mon humble avis est de séparer la logique de virtualisation du reste du composant (peut-être même un crochet?) accessoires de composants. Il permettra également d'exporter cette logique et permettra aux utilisateurs d'implémenter des composants personnalisés (comme la solution @ranneyd dans ce commentaire ) basés sur la même logique.

Les pensées?

@alonrbar ma solution, qui fait un positionnement absolu, donc aucune des grilles n'est celle qui fait le défilement, fonctionne très bien jusqu'à environ 300x300 où elle devient un peu lente (notez qu'elle reste synchronisée 100% du temps, le défilement juste devient un peu lent). À des tailles plus grandes, je pense qu'il s'agit simplement de traiter / mapper sur un grand tableau. Je pense qu'il y a plusieurs optimisations
Je peux le faire, cependant, et je ne suis pas entièrement convaincu que cela ait quelque chose à voir avec la synchronisation du défilement autant qu'avec mon implémentation de virtualisation assez simple. Je peux faire plus de mise en cache, par exemple, et je peux calculer si une cellule doit être virtualisée ou pas plus tôt pour éviter d'appeler du tout la fonction de rendu (et ensuite mieux la mettre en cache peut-être).

Je n'ai rien testé sur mobile. Je vais vous fournir un peu de code à essayer dans un instant.

Je veux vraiment regarder le test @bvaughn . Il dit que le fait de s'accrocher directement au parchemin natif le résout, vous dites que ce n'est pas le cas. Je veux voir par moi-même.

Pour ce qui est de tirer la logique de virtualisation dans un crochet ou une fonction indépendante, cela devient assez délicat car la logique est intrinsèquement connectée à la vue. Il semble également que de nombreux ajustements de performances impliquent des éléments de mise en cache et de mémorisation qui peuvent être difficiles à encapsuler dans un crochet ou une fonction, ou du moins dans une mesure où ils obtiennent les mêmes performances. Mais je vais regarder ce que j'ai et voir combien de logique je peux retirer.

PS :
Une chose à laquelle je viens de penser et qui ne fonctionnerait probablement pas, c'est de faire une chose semblable à la dénonciation + des transitions css. Si nous n'exécutons qu'un événement de défilement toutes les 100 ms ou quelque chose du genre et animons le mouvement, cela pourrait être mieux. Il peut également sembler nettement moins réactif. C'est un peu comme la façon dont ils ont fait World of Warcraft (s'il y a un décalage ou une latence élevée, ils feront bouger un personnage en ligne droite, puis le corrigeront une fois qu'ils auront obtenu l'information sur l'endroit où ils sont réellement allés).

Je veux vraiment regarder le test @bvaughn . Il dit que le fait de s'accrocher directement au parchemin natif le résout, vous dites que ce n'est pas le cas. Je veux voir par moi-même.

Je n'ai pas dit ça :smile: J'ai juste dit que cela évite un rendu inutile supplémentaire et une mutation DOM. L'impact sur les performances réelles n'est pas clair pour moi. Cependant, cela semble être un changement globalement positif.

@alonrbar @bvaughn J'ai besoin de documenter mon code, mais voici la dernière version :

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

@alonrbar @bvaughn alors voici une chose amusante que je viens de découvrir :

Ma solution ne fonctionne PAS dans Chrome 75 sur un écran de macbook. Lorsque mes collègues n'avaient pas mis à jour Chrome, cela fonctionnait. S'ils utilisent un moniteur externe, cela fonctionne. Sur leur écran d'ordinateur portable, il traîne.
??

Hmm... il y a quelques différences avec les moniteurs externes, avec le taux de rafraîchissement ou la mise à l'échelle. Quand tu dis que ça ne marche pas, qu'est-ce que tu veux dire concrètement ?

Ma faute. Je veux dire que le défilement n'est plus synchronisé. Si vous clonez mon référentiel et l'exécutez, vous pouvez comparer un écran externe à un écran d'ordinateur portable. Sur le moniteur, ils sont parfaitement synchronisés. Sur l'écran de l'ordinateur portable, les en-têtes clignotent (ne se mettent pas à jour à la même vitesse que l'élément de défilement).

J'ai en fait abandonné et j'essaye avec position: sticky . Cela fonctionne réellement. La prise en charge du navigateur n'est pas à 100 %, mais elle est en fait bien meilleure qu'elle ne l'était il y a un an.

Il y a cette bibliothèque qui est un polyfill non polyfill qui pourrait fonctionner, mais les navigateurs que nous ciblons prennent en charge cette fonctionnalité.
https://github.com/dollarshaveclub/stickybits

@alonrbar @bvaughn voici la dernière version. Il utilise position: sticky . Je l'explique un peu dans le readme. Le code doit encore être documenté.

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

Ma solution ne fonctionne PAS dans Chrome 75 sur un écran de macbook. Lorsque mes collègues n'avaient pas mis à jour Chrome, cela fonctionnait. S'ils utilisent un moniteur externe, cela fonctionne. Sur leur écran d'ordinateur portable, il traîne.
??

@ranneyd Il esprit aujourd'hui que cela pourrait être lié à la fréquence d'images. La fréquence d'images pour un moniteur externe peut être par exemple de 30 ips, auquel cas je pense que le navigateur peut envoyer moins d'événements de "défilement" (puisque je pense que la plupart des navigateurs n'en envoient qu'un par image/peinture). C'est peut-être pour cette raison que le décalage est plus perceptible pour vous sur un moniteur externe ?

@bvaughn Je pense que ça doit être ça. L'OS DIT qu'il envoie 60hz et je PENSE que les spécifications du moniteur disent 60hz, mais cela ne me surprendrait pas si quelqu'un ment 😂

@ranneyd Je suis désolé de dire que je ne suis pas très disponible en ce moment, donc je n'ai pas pu examiner correctement votre solution, mais d'un bref coup d'œil, j'ai remarqué que cela avait l'air plutôt bien et que vous avez créé un useVirtual hook et sépare élégamment la logique du rendu.
Je pense que ce serait génial si vous pouviez en quelque sorte créer une pull request pour react-window , l'utiliser là et peut-être exposer une méthode renderTable où vous pourriez brancher votre logique de rendu. De cette façon, votre bibliothèque pourrait simplement envelopper la fenêtre de réaction au lieu de la remplacer. Je pense qu'il serait préférable que nous puissions tirer parti de cette approche et résoudre le problème à l'intérieur de react-window qui est déjà à peu près testé au combat et largement répandu.

Sur un autre point, je pense que si scrollTo utilise la manipulation directe du DOM (peut-être en plus de définir l'état mais en tout cas avant de le faire), le résultat de la synchronisation de deux tables deviendra plus fluide. Si je ne me trompe pas, @bvaughn suggérait également quelque chose dans cette direction.

@ranneyd votre solution de table collante est vraiment géniale et je serais très heureux de pouvoir l'utiliser. Envisageriez-vous de publier une API utilisable pour cela sur npm ?

@bvaughn @ranneyd

Donc, après un certain temps, je suis récemment revenu à cette question. En utilisant le code et les idées de vos bibliothèques ainsi que de recyclerlistview et de react-virtual-grid, j'ai finalement pu obtenir le comportement que je voulais, c'est-à-dire une grille haute performance avec des lignes et des colonnes fixes qui fonctionne bien à la fois sur ordinateur de bureau et mobile périphériques (défilement fluide sans cellules vides/blanches).

Le TLDR est que j'utilise le recyclage avec le positionnement collant et le morceau de code le plus intéressant peut être trouvé ici dans cette méthode .

Les crédits et plus sur la motivation pour écrire encore une autre solution sont ici . Merci!

Je vais laisser un lien vers mon exemple de travail Code Sandbox ici au cas où quelqu'un d'autre tomberait sur ce problème à l'avenir.
https://codesandbox.io/s/y3pyp85zm1

Très agréable, jusqu'à ce que vous défiliez complètement vers la droite : alors les en-têtes sont mal alignés avec le contenu (de la largeur de la barre de défilement verticale de défilement).
Auriez-vous par hasard trouvé une solution à cela ?
Merci

Je vais laisser un lien vers mon exemple de travail Code Sandbox ici au cas où quelqu'un d'autre tomberait sur ce problème à l'avenir.
https://codesandbox.io/s/y3pyp85zm1

Très agréable, jusqu'à ce que vous défiliez complètement vers la droite : alors les en-têtes sont mal alignés avec le contenu (de la largeur de la barre de défilement verticale de défilement).
Auriez-vous par hasard trouvé une solution à cela ?
Merci

Divulgation complète : j'ai en fait créé ma propre version à partir de zéro. Il a sa propre virtualisation basée en grande partie sur cette bibliothèque. La raison pour laquelle j'ai fait cela est qu'il y a un problème sur les MacBooks et avec certains drapeaux Chrome où l'animation de défilement se produit avec un timing différent de celui du JS. L'utilisation de ScrollSync nécessitait trop d'appels de fonction et était trop lente. J'ai essentiellement dû refaire la bibliothèque avec des en-têtes collants en son cœur (je n'ai pas utilisé position: sticky comme je l'ai dit plus tôt dans ce fil. J'ai besoin de télécharger ce que j'ai maintenant à un moment donné).

Cela dit, la façon dont je contourne ce problème consiste soit à forcer une barre de défilement overflow: scroll , puis à ajouter ce remplissage à la dernière cellule, soit à déterminer dynamiquement s'il existe une barre de défilement (et quelle est sa largeur) à l'aide d'un ref et en y insérant un div invisible, en mesurant la taille de la barre de défilement et en supprimant le nœud DOM.

J'ai utilisé overflow-y: overlay qui fait la superposition de la barre de défilement. Malheureusement, cela ne fonctionne que pour les navigateurs Webkit.

Ok, merci pour l'information. Il semble vraiment que les en-têtes collants soient un besoin si courant que le simple ajout de l'option collante de la première ligne à la fenêtre de réaction en ferait un remplacement de la virtualisation de réaction dans de nombreux cas. Ne pensez-vous pas?

@ranneyd merci encore, j'ai suivi votre approche et la synchronisation du défilement fonctionne bien.

Le débordement masqué sur les barres de défilement provoque un désalignement près de la fin de la grille :

gridalignement

Des suggestions sur la façon de résoudre ce problème ?

Exemple de CodeSandbox

Merci d'avance

En retard à la fête, mais si vous ajoutez simplement une ligne à gaucheRef et mettez son débordement à masqué, vous résolvez essentiellement le pb. (Je l'ai fait aussi pour ne pas avoir à synchroniser le 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>
Cette page vous a été utile?
0 / 5 - 0 notes