6 votes

Comment implémenter des animations de réorganisation/mélange d'éléments avec ngFor d'Angular ?

Les utilisateurs de Vue sont faciles à mettre en œuvre de telles animations de shuffle d'éléments, consultez leur documentation officielle :

animation de shuffle

J'ai beaucoup cherché mais je ne trouve pas de solution pour les utilisateurs d'Angular. ngFor semble changer le contenu des éléments au lieu de déplacer les éléments lorsqu'ils sont mélangés.

Voici ma démo : http://embed.plnkr.co/3IcKcC/

Lorsque vous cliquez sur shift, vous devriez voir des animations de déplacement des éléments grâce à li {transform: all 1s;}. Mais lorsque vous les mélangez, il n'y a pas d'animation. Donc je suis ici à la recherche d'une solution.

13voto

yurzui Points 85802

Voici une implémentation simple de cette fonctionnalité Exemple Plunker

1) Construire des directives

@Directive({
  selector: '[transition-group-item]'
})
export class TransitionGroupItemDirective {
  prevPos: any;

  newPos: any;

  el: HTMLElement;

  moved: boolean;

  moveCallback: any;

  constructor(elRef: ElementRef) {
    this.el = elRef.nativeElement;
  }
}

@Component({
  selector: '[transition-group]',
  template: ''
})
export class TransitionGroupComponent {
  @Input('transition-group') class;

  @ContentChildren(TransitionGroupItemDirective) items: QueryList;

  ngAfterContentInit() {
    this.refreshPosition('prevPos');
    this.items.changes.subscribe(items => {
      items.forEach(item => {
        item.prevPos = item.newPos || item.prevPos;
      });

      items.forEach(this.runCallback);
      this.refreshPosition('newPos');
      items.forEach(this.applyTranslation);

      // forcer le reflow pour mettre tout en position
      const offSet = document.body.offsetHeight;
      this.items.forEach(this.runTransition.bind(this));
    })
  }

  runCallback(item: TransitionGroupItemDirective) {
    if(item.moveCallback) {
      item.moveCallback();
    }
  }

  runTransition(item: TransitionGroupItemDirective) {
    if (!item.moved) {
      return;
    }
    const cssClass = this.class + '-move';
    let el = item.el;
    let style: any = el.style;
    el.classList.add(cssClass);
    style.transform = style.WebkitTransform = style.transitionDuration = '';
    el.addEventListener('transitionend', item.moveCallback = (e: any) => {
      if (!e || /transform$/.test(e.propertyName)) {
        el.removeEventListener('transitionend', item.moveCallback);
        item.moveCallback = null;
        el.classList.remove(cssClass);
      }
    });
  }

  refreshPosition(prop: string) {
    this.items.forEach(item => {
      item[prop] = item.el.getBoundingClientRect();
    });
  }

  applyTranslation(item: TransitionGroupItemDirective) {
    item.moved = false;
    const dx = item.prevPos.left - item.newPos.left;
    const dy = item.prevPos.top - item.newPos.top;
    if (dx || dy) {
      item.moved = true;
      let style: any = item.el.style;
      style.transform = style.WebkitTransform = 'translate(' + dx + 'px,' + dy + 'px)';
      style.transitionDuration = '0s';
    }
  }
}

2) Utilisez-le comme suit

    {{ item }}

2voto

pawel Points 41

Voici ma version du code de @yurzui. Changements :

  • prend en charge l'insertion et la suppression d'éléments
  • une reprise forcée survive aux optimisations de webpack

    import { Component, ContentChildren, Directive, ElementRef, Input, QueryList } from '@angular/core';

    @Directive({ selector: '[transition-group-item]' }) export class TransitionGroupItemDirective { prevPos: any; newPos: any; el: HTMLElement; moved: boolean; moveCallback: any;

    constructor(elRef: ElementRef) {
        this.el = elRef.nativeElement;
    }

    }

    @Component({ selector: '[transition-group]', template: '' }) export class TransitionGroupComponent { @Input('transition-group') class;

    @ContentChildren(TransitionGroupItemDirective) items: QueryList;
    
    ngAfterViewInit() {
        setTimeout(() => this.refreshPosition('prevPos'), 0); // enregistrer les positions initiales au prochain 'tick'
    
        this.items.changes.subscribe(items => {
            items.forEach(item => item.prevPos = item.newPos || item.prevPos);
            items.forEach(this.runCallback);
            this.refreshPosition('newPos');
            items.forEach(item => item.prevPos = item.prevPos || item.newPos); // pour les nouveaux éléments
    
            const animer = () => {
                items.forEach(this.applyTranslation);
                this['_forceReflow'] = document.body.offsetHeight; // forcer le repositionnement pour mettre tout en place
                this.items.forEach(this.runTransition.bind(this));
            }
    
            const vaBouger = items.some((item) => {
                const dx = item.prevPos.left - item.newPos.left;
                const dy = item.prevPos.top - item.newPos.top;
                return dx || dy;
            });
    
            if (vaBouger) {
                animer();
            } else {
                setTimeout(() => { // pour les éléments supprimés
                    this.refreshPosition('newPos');
                    animer();
                }, 0);
            }
        })
    }
    
    runCallback(item: TransitionGroupItemDirective) {
        if (item.moveCallback) {
            item.moveCallback();
        }
    }
    
    runTransition(item: TransitionGroupItemDirective) {
        if (!item.moved) {
            return;
        }
        const cssClass = this.class + '-move';
        let el = item.el;
        let style: any = el.style;
        el.classList.add(cssClass);
        style.transform = style.WebkitTransform = style.transitionDuration = '';
        el.addEventListener('transitionend', item.moveCallback = (e: any) => {
            if (!e || /transform$/.test(e.propertyName)) {
                el.removeEventListener('transitionend', item.moveCallback);
                item.moveCallback = null;
                el.classList.remove(cssClass);
            }
        });
    }
    
    refreshPosition(prop: string) {
        this.items.forEach(item => {
            item[prop] = item.el.getBoundingClientRect();
        });
    }
    
    applyTranslation(item: TransitionGroupItemDirective) {
        item.moved = false;
        const dx = item.prevPos.left - item.newPos.left;
        const dy = item.prevPos.top - item.newPos.top;
        if (dx || dy) {
            item.moved = true;
            let style: any = item.el.style;
            style.transform = style.WebkitTransform = 'translate(' + dx + 'px,' + dy + 'px)';
            style.transitionDuration = '0s';
        }
    }

    }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X