【问题标题】:Transitions in AngularAngular 中的过渡
【发布时间】:2019-05-23 10:28:00
【问题描述】:

我在 Angular 中创建了一个布局管理器,它可以接收组件,然后在视图中显示它并向其中添加动画,同时每个组件都显示在视图中并退出视图。

在单个时间实例中可以显示一个面板或最多两个面板。

这是Stackblitz link 相同的问题是过渡不平滑,而且它确实看起来像它应该的流线型设计如下。

现在我想要实现的是,当应用程序加载时,默认显示 1-2,但是当我更改面板时,过渡会发生变化,例如,

1-3 因为 2 移出视野,它应该向左滑动并缓出,3 应该进入并缓出。然后如果从 1-3 到 2-3 1 应该向右缓出,2 应该滑入。

面板也可以占屏幕宽度的某些百分比(33%、66% 或 100%)。

我不确定我是否能够正确解释它我已经坚持了好几个星期了,如果有人能提供帮助,那就太棒了,谢谢

感谢 萨达姆,他帮助创建了这个动画,这正是我想要的动画 - https://imgur.com/a/qZ3vtDb 这仅用于视觉目的

【问题讨论】:

  • 请详细说明您的问题,例如 1-2 是 panel1 和 panel2 加载页面时它们会发生什么,过渡将如何。
  • 当页面加载时,我们可以有一个从左到右的过渡,或者当页面第一次加载时根本没有过渡,然后过渡开始取决于哪个面板在视图中和不在视图中
  • 你尝试过角度动画吗?
  • 不幸的是,除了某种动画之外,我还没有设法了解您到底想要实现什么。但是,您可以尝试学习 Angular Animations,它应该对 blog.angularindepth.com/… 有所帮助。此外,如果您制作了某种动画(在任何图形编辑器中),我们就有可能更好地理解您的愿望。
  • 我对动画进行了很多详细的研究,并多次浏览过,我同意你的观点,图形动画真的很有帮助,我会尝试看看这是可能的,因为我不是精通任何图形编辑器

标签: angular css-transitions angular-animations


【解决方案1】:

我已更改 StackBlitz 示例中的 PanelComponent 以按照提供的动画显示方式工作。

您只需要三个状态。当组件最初位于右侧外部时的一种。从那里它进入视野,这是第二个状态。之后,它向左移到第三个状态的视野之外。一旦它离开了左侧的视野,您就可以将其移回右侧的初始状态,以便在需要时重新进入。

这里是更改后的面板组件的代码:

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 拆分为setInViewStylemoveOut 两种方法。 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 sample 的链接,它实现了所有这些,因此您也可以看到它正在工作。

根据评论中的要求,我还提供了另一个双向移动示例。这使事情变得更加复杂。我不得不为另一个方向的移动再添加一个过渡。并添加了在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 sample

【讨论】:

  • 真的很感谢你,我猜你已经搞定了,但我想问一个小问题。在示例中,当我们给出面板 1 和 2,然后转到面板 2 和 3 时效果很好,但是从该视图中,当我们再次移动到面板 1 和 2 时,面板 1 应该来自左侧,难道你不认为它来自右侧.任何指针我们如何控制方向,因为面板的顺序对我们来说是固定的 1 -> 2 -> -> 3- >4
  • 如果我们可以在这里给出过渡的方向,请告诉我,因为面板是有序的,它看起来很棒。即使您对此无法提供任何帮助,我也会非常感谢您。期待您的回复
  • 我在答案中添加了另一个实现,您可以在其中指定动画的方向。
猜你喜欢
  • 1970-01-01
  • 2018-07-13
  • 2015-06-03
  • 1970-01-01
  • 2016-02-02
  • 2014-01-29
  • 2018-06-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多