web-dev-qa-db-fra.com

Qu'est-ce qu'un moyen convivial pour animer une réorganisation de la liste?

J'ai une liste d'éléments avec les scores, classés par scores, rendus par react.js sous forme d'une liste d'éléments rectangulaires orientés verticalement (les scores les plus élevés en haut). Les survols et autres clics sur les éléments individuels peuvent afficher/masquer des informations supplémentaires, en modifiant leur hauteur verticale.

De nouvelles informations arrivent qui modifient légèrement les scores, faisant en sorte que certains éléments soient mieux classés et d’autres plus bas après un nouveau tri. Je voudrais que les éléments animent simultanément à leurs nouvelles positions, plutôt que d'apparaître instantanément dans leurs nouveaux emplacements. 

Existe-t-il une méthode recommandée dans React.js, éventuellement avec un add-on populaire?

(Dans une situation antérieure similaire utilisant D3, une technique que j'ai utilisée était approximativement:

  1. Affichez la liste avec les nœuds DOM d’article dans leur ordre naturel, avec positionnement relatif. Avec le positionnement relatif, d’autres modifications mineures - déclenchées par CSS ou JS - en fonction de l’ampleur de chaque élément en modifient d’autres, comme prévu.
  2. Mutez tous les nœuds DOM, en une seule étape, pour que leurs coordonnées relatives réelles deviennent de nouvelles coordonnées absolues - un changement DOM qui ne provoque aucun changement visuel.
  3. Réordonnez les nœuds DOM d'élément, au sein de leur parent, dans le nouvel ordre de tri: un autre changement DOM ne provoquant aucun changement visuel.
  4. Animez les top-offsets de tous les nœuds avec leurs nouvelles valeurs calculées, en fonction de la hauteur de tous les éléments précédents dans le nouvel ordre. C'est la seule étape visuellement active.
  5. Mutatez tous les noeuds DOM d'élément en position relative non décalée. Là encore, cela ne provoque aucun changement visuel, mais les nœuds DOM, qui sont maintenant relativement positionnés, dans l'ordre naturel de la liste sous-jacente, gèrent les changements de style internes survolés/développés/etc. avec un décalage correct.

Maintenant, j'espère un effet similaire d'une manière très réaliste ...)

39
gojomo

Je viens de publier un module pour aborder exactement ce problème

https://github.com/joshwcomeau/react-flip-move

Il fait certaines choses différemment de Magic Move/Shuffle:

  1. Il utilise la technique FLIP pour les animations 60FPS + à accélération matérielle.
  2. Il offre des options pour "humaniser" le remaniement en décalant progressivement le délai ou la durée
  3. Il gère les interruptions avec élégance, sans effets de pépin étranges 
  4. Un tas d'autres choses intéressantes comme les rappels de début/fin

Découvrez les démos:

http://joshwcomeau.github.io/react-flip-move/examples/#/shuffle

27
Joshua Comeau

React Shuffle est solide et à jour. C'est inspiré par la démo Ryan Florences Magic Move 

https://github.com/FormidableLabs/react-shuffle

9
Tim Arney

Je me rends compte qu’il s’agit d’une question un peu ancienne et que vous avez probablement déjà trouvé une solution, mais si vous avez une question, essayez la bibliothèque MagicMove de Ryan Florence. C'est une bibliothèque React pour gérer le scénario exact que vous décrivez: https://github.com/ryanflorence/react-magic-move

Voyez-le en action: https://www.youtube.com/watch?v=z5e7kWSHWTg#t=424

Edit: Ceci est cassé dans React 0.14. Voir Réagir Mélanger comme une alternative, comme suggéré par Tim Arney ci-dessous.

9
Andru

La meilleure façon dont je puisse penser à accomplir ceci spontanément serait tout d'abord de vous assurer que vous donniez à chacun de vos éléments une clé comme décrit ici:

http://facebook.github.io/react/docs/multiple-components.html#dynamic-children

Ensuite, je définirais une animation CSS3 similaire à celle-ci:

Animer des listes avec CSS3

Essentiellement, dans votre fonction de rendu, vous calculeriez où l'élément est supposé aller, ReactJS placera l'élément là où il est supposé être (assurez-vous de donner à chaque élément la même clé à chaque fois! Il est important de vous assurer que ReactJS réutilise votre élément DOM correctement). En théorie du moins, le CSS devrait s'occuper du reste de l'animation pour vous en dehors de reactjs/javascript.

Disclamer ... Je n'ai jamais vraiment essayé ceci;)

3
Chris

Étant donné que la position d'un élément dépend fortement du moteur DOM, il est très difficile d'implémenter l'interpolation et l'animation de manière pure dans les composants React. Si vous voulez le faire proprement, je pense qu'il vous faut au moins: un magasin pour conserver la position actuelle d'un élément, un magasin pour conserver la position suivante d'un élément (ou le combiner avec le magasin précédent) et un rendu ( ) méthode qui applique les marges de décalage par élément jusqu’à ce que les positions suivantes deviennent actuelles dans la dernière image. Comment calculer les prochaines positions en premier lieu est également un défi. Mais étant donné que les éléments n’échangent que des positions, vous pouvez utiliser le magasin avec les positions actuelles pour échanger l’élément n ° 0 avec l’élément n ° 1, en échangeant leurs décalages dans le magasin suivant.

En fin de compte, il est plus facile d'utiliser une bibliothèque externe pour manipuler les marges des éléments après leur rendu.

0
Rygu

Je ne souhaitais pas utiliser de bibliothèque tierce pour mes animations. J'ai donc implémenté la technique d'animation " FLIP " à l'aide des méthodes de cycle de vie de React, getSnapshotBeforeUpdate () et composantDidUpdate ().

Lorsque props.index de l'élément de liste change, l'ancienne position est capturée dans les méthodes getSnapshotBeforeUpdate () et composantDidUpdate (), la transformation css: translateY () est appliquée de sorte que l'élément semble s'animer d'ancienne position à la position actuelle.

class TreeListItem extends Component {

  constructor(props) {
    super(props);
    this.liRef = React.createRef();
  }

  getSnapshotBeforeUpdate(prevProps) {
    if (prevProps.index !== this.props.index) {
      // list index is changing, prepare to animate
      if (this.liRef && this.liRef.current) {
        return this.liRef.current.getBoundingClientRect().top;
      }
    }
    return null;
  }


  componentDidUpdate(prevProps, prevState, snapshot) {
    if (prevProps.index !== this.props.index && snapshot) {
      if (this.liRef && this.liRef.current) {
        let el = this.liRef.current;
        let newOffset = el.getBoundingClientRect().top;
        let invert = parseInt(snapshot - newOffset);
        el.classList.remove('animate-on-transforms');
        el.style.transform = `translateY(${invert}px)`;
        // Wait for the next frame so we
        // know all the style changes have
        // taken hold.
        requestAnimationFrame(function () {
          // Switch on animations.
          el.classList.add('animate-on-transforms');
          // GO GO GOOOOOO!
          el.style.transform = '';
        });
      }
    }
  }

  render() {
    return <li ref={this.liRef}>
    </li >;
  }
}

class TreeList extends Component {

  render() {
    let treeItems = [];
    if (this.props.dataSet instanceof Array) {
      this.props.dataSet.forEach((data, i) => {
        treeItems.Push(<TreeListItem index={i} key={data.value} />)
      });
    }

    return <ul>
      {treeItems}
      </ul>;
  }
}
.animate-on-transforms {
  /* animate list item reorder */
  transition: transform 300ms ease;
}

0
Boog