Переходы в угловые

Я создал менеджер раскладок в Angular, который может принимать компоненты, а затем отображать его в представлении и добавлять анимацию, пока каждый компонент отображается в представлении и выходит из поля зрения.

В один и тот же момент времени может быть показана одна панель или максимум две панели.

Это Stackblitz link на ту же проблему, проблема в том, что переходы не являются плавными, а также выглядят такими же обтекаемыми, как и должно быть, дизайн выглядит следующим образом.

Design

Теперь я пытаюсь добиться того, чтобы приложение загружалось, и по умолчанию показывается 1-2, но когда я меняю панели, переходы меняются, как, например,

1-3, так как 2 выходит из поля зрения, он должен сдвинуться влево и ослабить, а 3 должен войти и ослабить. и затем, если с 1-3 мы идем к 2-3, 1 должен сместиться вправо и 2 должен скользить внутрь.

Также панели могут занимать несколько процентов (33%, 66% или 100%) от ширины экрана.

Я не уверен, смогу ли я объяснить это должным образом, я застрял на этом в течение нескольких недель с переходами, если кто-то может помочь, это будет здорово, спасибо

Благодаря Саддаму, который помог создать эту анимацию, это именно то, что я хочу от анимации - https://imgur.com/a/qZ3vtDb это только для визуальных целей

Ответы

Ответ 1

Я изменил PanelComponent в вашем образце StackBlitz, чтобы он работал так, как показывает предоставленная анимация.

Вам нужно всего три состояния. Один, когда компонент изначально снаружи справа. Оттуда это становится очевидным, это второе состояние. После этого он выходит из поля зрения третьего состояния. Как только он окажется вне поля зрения слева, вы вернете его в правильное исходное состояние, чтобы он мог вернуться, когда это необходимо.

Вот код для измененного компонента панели:

import { Component, ContentChild, QueryList,HostBinding,Input,ElementRef } from '@angular/core';
import {
  trigger,
  state,
  style,
  animate,
  transition
} from '@angular/animations';

@Component({
  selector: 'my-panel',
  templateUrl: './panel.component.html',
  styleUrls:['./panel.component.css'],
  animations: [
    trigger('transition', [
      state('right', style({
        transform: 'translateX(100%)',
        opacity: 0
      })),
      state('inview', style({
      })),
      state('left', style({
        transform: 'translateX(-100%)',
        opacity: 0
      })),
      transition('right => inview', [
        animate('${PanelComponent.ANIMATION_DURATION}ms 0ms ease-out',style({ 
          transform: 'translateX(0)',
          opacity: 1 }))
      ]),
      transition('inview => left', [
        animate('${PanelComponent.ANIMATION_DURATION}ms 0ms ease-in',style({ 
          transform: 'translateX(-100%)',
          opacity: 0 }))
      ])
    ])]
})
export class PanelComponent  {
  public static readonly ANIMATION_DURATION = 500;
  @Input() destroyOnHidden: boolean = false;
  @Input() id : string = null;
  @ContentChild('layout') contentChild : QueryList<ElementRef>;
  @HostBinding('style.width') componentWidth = null;
  @HostBinding('style.height') componentHeight = null;
  @HostBinding('style.overflow') componentOverflow = null;
  public state: string = null;

  public getId() {
    return this.id;
  }

  constructor() {
    this.state = 'right';
  }

  public setInViewStyle(width: string, height: string, overflow: string): void {
    this.componentWidth = width + '%';
    this.componentHeight = height + '%';
    this.componentOverflow = overflow;
    this.state = 'inview';
  }

  public setDefault(): void {
    this.state = 'right';
  }

  public moveOut(): void {
    this.state = 'left';
  }


  public transitionDoneHide(): void {
    if(this.state === 'right') {
      console.log('hiding transition done');
      this.componentWidth = '0' + '%';
      this.componentHeight = '0' + '%';
      this.componentOverflow = 'hidden';
    }
  }
}

Как видите, я разделил setStyle на два метода setInViewStyle и moveOut. setInViewStyle устанавливает стиль панели и перемещает его в представление. Метод moveOut перемещает панель из поля зрения влево. Из-за этого я также изменил метод менеджера макета panelTransformation.

Вот измененный код:

panelTransformation(transitions) {
    if (transitions) {
      let movement = null;
      let panelsToRemove = [];
      let panelsToAdd = [];
      if (this.previousPanels) {
        panelsToRemove = this.previousPanels.filter((panel) => transitions.panel.indexOf(panel) < 0);
        panelsToAdd = transitions.panel.filter((panel) => this.previousPanels.indexOf(panel) < 0);
      } else {
        panelsToAdd = transitions.panel
      }

      if (panelsToRemove.length > 0) {
        for (let panelToRemove of panelsToRemove) {
          this.idPanelMap.get(panelToRemove).moveOut();
        }
        // wait for fade out to finish then start fade in
        timer(PanelComponent.ANIMATION_DURATION + 100).subscribe(() => {
          for (let panelToAdd of panelsToAdd) {
            this.idPanelMap.get(panelToAdd).setInViewStyle(transitions.width[transitions.panel.indexOf(panelToAdd)], '100', 'initial');
          }
          for (let panelToRemove of panelsToRemove) {
            this.idPanelMap.get(panelToRemove).setDefault();
          }
        });
      } else { // first time so just fade in
        for (let panelToAdd of panelsToAdd) {
          this.idPanelMap.get(panelToAdd).setInViewStyle(transitions.width[transitions.panel.indexOf(panelToAdd)], '100', 'initial');
        }
      }

      this.previousPanels = transitions.panel;
    }
  }

Как вы можете видеть, я полностью изменил логику, поэтому сначала вынимаю панели, которые нужно удалить, подождите, пока закончится анимация, а затем переместите новые панели. Вот ссылка на мой пример StackBlitz, который реализует все это, так что вы также можете увидеть, как он работает.

В соответствии с просьбой в комментарии я также приведу еще один пример движения в обоих направлениях. Это усложняет ситуацию. Мне пришлось добавить еще один переход для движения в другом направлении. И добавлена возможность установить направление в методе moveOut. По моему мнению, это не выглядит так хорошо, потому что может иметь некоторое мерцание. Новый код компонента панели:

import { Component, ContentChild, QueryList,HostBinding,Input,ElementRef } from '@angular/core';
import {
  trigger,
  state,
  style,
  animate,
  transition
} from '@angular/animations';
import { timer } from 'rxjs';

@Component({
  selector: 'my-panel',
  templateUrl: './panel.component.html',
  styleUrls:['./panel.component.css'],
  animations: [
    trigger('transition', [
      state('right', style({
        transform: 'translateX(100%)',
        opacity: 0
      })),
      state('inview', style({
      })),
      state('left', style({
        transform: 'translateX(-100%)',
        opacity: 0
      })),
      transition('right => inview', [
        animate('${PanelComponent.ANIMATION_DURATION}ms 0ms ease-out',style({ 
          transform: 'translateX(0)',
          opacity: 1 }))
      ]),
      transition('inview => left', [
        animate('${PanelComponent.ANIMATION_DURATION}ms 0ms ease-in',style({ 
          transform: 'translateX(-100%)',
          opacity: 0 }))
      ]),
      transition('inview => right', [
        animate('${PanelComponent.ANIMATION_DURATION}ms 0ms ease-in',style(         { 
          transform: 'translateX(100%)',
          opacity: 0
       }))
      ]),
      transition('left => inview', [
        animate('${PanelComponent.ANIMATION_DURATION}ms 0ms ease-out',style({ 
          transform: 'translateX(0)',
          opacity: 1 }))
      ])
    ])]
})
export class PanelComponent  {
  public static readonly ANIMATION_DURATION = 500;
  public static readonly ANIMATION_DELAY = 100;
  @Input() destroyOnHidden: boolean = false;
  @Input() id : string = null;
  @ContentChild('layout') contentChild : QueryList<ElementRef>;
  @HostBinding('style.width') componentWidth = null;
  @HostBinding('style.height') componentHeight = null;
  @HostBinding('style.overflow') componentOverflow = null;
  public state: string = null;
  private lastDirection: 'left' | 'right';

  public getId() {
    return this.id;
  }

  constructor() {
    this.state = 'right';
  }

  public setInViewStyle(width: string, height: string, overflow: string): void {
    this.componentWidth = width + '%';
    this.componentHeight = height + '%';
    this.componentOverflow = overflow;
    this.state = 'inview';
  }

  public setDefault(): void {
    this.state = 'right';
  }

  public moveOut(direction: 'left' | 'right'): void {
    this.lastDirection = direction;
    this.state = direction;
  }


  public transitionDoneHide(): void {
    if(this.state === this.lastDirection) {
      if (this.lastDirection === 'right') {
        timer(PanelComponent.ANIMATION_DELAY).subscribe(() => this.hide());
      } else {
        this.hide();
      }
      console.log('hiding transition done');

    }
  }

  private hide() {
    this.componentWidth = '0' + '%';
    this.componentHeight = '0' + '%';
    this.componentOverflow = 'hidden';
  }
}

В методе panelTransformation я добавил логику, чтобы задать направление движения вправо, если это первая панель. Это обновленный код:

panelTransformation(transitions) {
  if (transitions) {
    let movement = null;
    let panelsToRemove = [];
    let panelsToAdd = [];
    if (this.previousPanels) {
      panelsToRemove = this.previousPanels.filter((panel) => transitions.panel.indexOf(panel) < 0);
      panelsToAdd = transitions.panel.filter((panel) => this.previousPanels.indexOf(panel) < 0);
    } else {
      panelsToAdd = transitions.panel
    }

    if (panelsToRemove.length > 0) {
      for (let panelToRemove of panelsToRemove) {
        let direction: 'left' | 'right' = 'left';
        // if it is the first panel we move out right
        if (this.previousPanels.indexOf(panelToRemove) === 0) {
          direction = 'right';
        }
        this.idPanelMap.get(panelToRemove).moveOut(direction);
      }
      // wait for fade out to finish then start fade in
      timer(PanelComponent.ANIMATION_DURATION + PanelComponent.ANIMATION_DELAY).subscribe(() => {
        for (let panelToAdd of panelsToAdd) {
          this.idPanelMap.get(panelToAdd).setInViewStyle(transitions.width[transitions.panel.indexOf(panelToAdd)], '100', 'initial');
        }
        for (let panelToRemove of panelsToRemove) {
          this.idPanelMap.get(panelToRemove).setDefault();
        }
      });
    } else { // first time so just fade in
      for (let panelToAdd of panelsToAdd) {
        this.idPanelMap.get(panelToAdd).setInViewStyle(transitions.width[transitions.panel.indexOf(panelToAdd)], '100', 'initial');
      }
    }

    this.previousPanels = transitions.panel;
  }
}

А также образец StackBlitz для этой реализации.