【问题标题】:How to animate akin to :enter and :leave when object reference changes如何在对象引用更改时制作类似于 :enter 和 :leave 的动画
【发布时间】:2021-07-13 05:14:21
【问题描述】:

这是一些工作代码:

Stackblitzhttps://stackblitz.com/edit/angular-ivy-tt9vjd?file=src/app/app.component.ts

app.component.html

<button (click)='swap()'>Swap object</button>
<div @div *ngIf='object'>{{ object.data }}</div>

app.component.css

div {
  width: 100px;
  height: 100px;
  background: purple;
  display: flex;
  align-items: center;
  justify-content: center;
  color: antiquewhite;
}

app.component.ts

import { animate, style, transition, trigger } from "@angular/animations";
import { Component } from "@angular/core";
import { interval } from "rxjs";
import { first } from "rxjs/operators";

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"],
  animations: [
    trigger("div", [
      transition(":enter", [
        style({ transform: "scale(0)" }),
        animate("250ms 0ms ease-out", style({ transform: "scale(1)" }))
      ]),
      transition(":leave", [
        style({ transform: "scale(1)" }),
        animate("250ms 0ms ease-in", style({ transform: "scale(0)" }))
      ])
    ])
  ]
})
export class AppComponent {
  object: any = { data: "DIV_1" };

  swap() {
    this.object = null;
    interval(0)
      .pipe(first())
      .subscribe(() => {
        this.object = { data: "DIV_2" };
      });

    // DESIRED CODE INSTEAD OF THE ABOVE
    // this.object = { data: "DIV_2" };
  }
}

此代码的问题是必须引入中间 null 状态。因此,我将演示代码与逻辑混合以“使其工作”。这违反了良好的封装实践,并为代码增加了不必要的复杂性。

如何在装饰器的 animations 属性中使用代码来获得相同的结果?

要求

  • 检测对象引用的变化。
  • 对对象引用更改做出反应,类似于:enter:leave 的工作方式;首先将DIV_1 动画化,然后将DIV_2 动画化。
  • animations 代码中封装有关动画的任何内容。所以交换函数应该是:swap(){this.object = { data: "DIV_2" };}

【问题讨论】:

    标签: angular angular-animations


    【解决方案1】:

    只是想分享我最终得到的结果。该解决方案的灵感主要来自 Francesco Lisandro 的回答。

    • 已创建结构指令
    • 该指令采用具有 2 个属性的对象
      • 数据:引用发生变化的对象
      • leaveTransitionDuration:延迟插入空状态的持续时间
    • 该指令在旧对象之后和新对象创建之前插入一个 void 状态。在 leaveTransitionDuration 之后插入 void 状态。
    • 当引用从虚假值更改为对象时,该指令不会应用 leaveTransitionDuration。所以常规的:enter 动画不会延迟。

    app.component.html

    <div @div *ngIfAnimation="{data: object, leaveTransitionDuration: 2000}">{{ object.data }}</div>
    

    app.component.ts

    import { Component } from "@angular/core";
    import { animate, style, transition, trigger } from "@angular/animations";
    
    @Component({
      selector: "my-app",
      templateUrl: "./app.component.html",
      styleUrls: ["./app.component.css"],
      animations: [
        trigger("div", [
          transition(":enter", [
            style({ transform: "scale(0)" }),
            animate("500ms 0s ease-in", style({ transform: "scale(1)" }))
          ]),
          transition(":leave", [
            style({ transform: "scale(1)" }),
            animate("2s ease-in", style({ transform: "scale(0)" }))
          ])
        ])
      ]
    })
    export class AppComponent {
      object: any = { data: "DIV_1" };
    
      swap() {
        this.object = { data: "DIV_2" };
      }
    }
    

    ngIfAnimation.diretive.ts

    import { Directive, Input, TemplateRef, ViewContainerRef } from "@angular/core";
    import { interval } from "rxjs";
    import { first } from "rxjs/operators";
    
    @Directive({
      selector: "[ngIfAnimation]"
    })
    export class NgIfAnimationDirective {
      constructor(private view: ViewContainerRef, private tmpl: TemplateRef<any>) {}
      private old_data = null;
      private leaveTransitionDuration: number;
    
      @Input() set ngIfAnimation(object: {
        data: any;
        leaveTransitionDuration: number;
      }) {
        this.leaveTransitionDuration = object.leaveTransitionDuration;
        this.view.clear();
        if (object.data) this.add_new_view();
        this.old_data = object.data;
      }
    
      private get delay(): number {
        return this.old_data ? this.leaveTransitionDuration : 0;
      }
      private add_new_view(): void {
        interval(this.delay) // timeout until `:leave` animation is ran
          .pipe(first())
          .subscribe(() => {
            this.view.createEmbeddedView(this.tmpl);
          });
      }
    }
    

    查看 Stackblitz 以获取演示:https://stackblitz.com/edit/angular-ivy-t7ea53?file=src/app/ngIfAnimation.directive.ts

    注意:确保 leaveTransitionDuration 等于 :leave 过渡的持续时间。

    【讨论】:

      【解决方案2】:

      所以,我花了一点时间来解决,但我找到了一个可以帮助您的可能解决方案。你应该创建一个自定义指令来代替经典的ngIf

      import {
        Directive,
        Input,
        TemplateRef,
        ViewContainerRef
      } from '@angular/core';
      
      @Directive({
        selector: '[ngIfAnimation]'
      })
      export class NgIfAnimationDirective {
        private value: any;
        private hasView = false;
      
        constructor(
          private view: ViewContainerRef,
          private tmpl: TemplateRef<any>
        ) { }
      
        @Input() set ngIfAnimation(val: any) {
          if (!this.hasView) {
            this.view.createEmbeddedView(this.tmpl);
            this.hasView = true;
          } else if (val !== this.value) {
            this.view.clear();
            this.view.createEmbeddedView(this.tmpl);
            this.value = val;
          }
        }
      }
      

      快速:我们清除当前视图,实例化一个嵌入式视图,并在每次发生变化时将其插入到 div 中。 关于您的组件,您的动画将是这样的(您当然可以更改它们):

      animations: [
          trigger('div', [
            state('void', style({ transform: 'scale(0)' })),
            state('*', style({transform: 'scale(1)' })),
            transition('void => *', [animate('0.2s 0.2s ease-in')]),
            transition('* => void', [animate('0.2s ease-in')])
          ])
        ],
      

      还有你的模板:

      <div [@div] *ngIfAnimation="object">{{ object.data }}</div>
      

      记得删除你的*ngIf,因为你不能在一个元素上绑定多个模板。

      Stackblitz:https://stackblitz.com/edit/angular-ivy-hc8snt?file=src/app/app.component.html

      【讨论】:

      • 感谢您的宝贵时间,但这与:enter:leave 不同。请注意,您的解决方案中的内容会立即更改为DIV_2。我希望对象消失,然后更改值,然后重新出现。您在 stackblitz 中应用的解决方案:stackblitz.com/edit/angular-ivy-bv82db?file=src/app/…
      • 已更新 ;) 谢谢。这很棘手但很有趣。希望对你有帮助
      • 成功了 :) 我玩弄了它并发布了我最终得到的结果。
      猜你喜欢
      • 1970-01-01
      • 2021-07-25
      • 1970-01-01
      • 2021-11-11
      • 2020-06-28
      • 2020-03-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多