Ionic/Angular + Lottie:分别为每个 Lottie 制作动画?

Ionic/Angular + Lottie: animate each Lottie individually?

在我继续之前,我应该注意到我之前发布了一个类似的问题,内容与这个类似,但是我认为错误的来源是使用ngFor 循环内的自定义组件。我发现这不是问题,所以我删除了原来的问题以使我的查询更准确并且不误导任何人。

我目前正在使用 Ionic 5 和 Angular 9 进行一个项目,我们必须在某些视图中使用 lotties。我正在使用 ngx-lottie 库 (https://github.com/ngx-lottie/ngx-lottie#api) 并且出于我的目的,我没有像库的 API 所示那样在每个视图的模板中插入 lottie,而是构建了一个要包含在其中的组件其他意见。该组件的代码如下:

import { Component, OnInit, OnChanges, SimpleChanges, Input } from '@angular/core';
import { AnimationItem } from 'lottie-web';
import { AnimationOptions } from 'ngx-lottie';

@Component({
  selector: 'app-lottie-player',
  template: `<ng-lottie
  [options]="options"
  [styles]="styles"
  (animationCreated)="animationCreated($event)">
  </ng-lottie>`,
})

export class LottiePlayerComponent implements OnInit, OnChanges {

  @Input() lottieToPlay: string;        // Route to the json within assets/lotties
  @Input() lottieToUpdate: string;      // Secondary json in case a switch is needed
  @Input() animationDirection: number;  // 1 for normal direction, -1 for reversed animation
  @Input() dimensions: any;             // Layout specifications passed from parent component

  options: AnimationOptions = {
    path: '',
  };

  styles: Partial<CSSStyleDeclaration> = {
    margin: '0 auto',
  };

  private animationItem: AnimationItem;

  constructor() { }

  ngOnInit() {
    this.updateAnimation();
  }

  ngOnChanges(changes: SimpleChanges) {
    const directionChange = changes.animationDirection;
    if (directionChange && !directionChange.firstChange) {
      this.playAnimation(this.animationItem, directionChange.currentValue);
    }
  }

  updateAnimation(): void {
    this.options = {
      ...this.options,
      path: this.lottieToPlay,
    };
    this.styles = {
      ...this.styles,
      ...this.dimensions
    };
  }

  animationCreated(animationItem: AnimationItem): void {
    this.animationItem = animationItem;
    this.animationItem.autoplay = false;
    this.animationItem.loop = false;
  }

  playAnimation(animationItem: AnimationItem, direction?: number): void {
    this.animationItem = animationItem;
    this.animationItem.setDirection(Math.sign(direction) === 1 ? 1 : -1);
    this.animationItem.play();
  }

}

该组件主要执行以下操作:

所以当我将它插入到这样的视图中时:

    <ion-buttons slot="secondary">
      <ion-button class="icon-container" (click)="changeIconAndNavigate()">
        <app-lottie-player
          [dimensions]="lottieClassSpecs"
          [lottieToPlay]="lottieFav"
          [animationDirection]="lottieAnimDirection">
        </app-lottie-player>
      </ion-button>
    </ion-buttons>

在父组件中有这样一个方法:

  public changeIconAndNavigate() {
    this.lottieAnimDirection = 1;
  }

lottie 按预期运行。动画只触发一次并保持在完成动画状态。

当我像上面那样插入 lottie 时出现问题,但在同一个组件中插入了多个而不是一个。我现在相信问题的原因可能是我构建了错误的原始组件,可以这么说。在另一个组件中,我像这样插入了 lottie-player:

<ion-item *ngFor="let card of cards" class="card-info">
...
          <ion-item lines="full">
            <span class="card-span">{{ card.title }}</span>
            <ion-button class="lottie-holder" (click)="toggleSingleSubscription(card)">
              <app-lottie-player
              [dimensions]="lottieClassSpecs"
              [lottieToPlay]="lottieAdd"
              [animationDirection]="lottieAnimDirection">
              </app-lottie-player>
            </ion-button>
          </ion-item>
...
</ion-item>

为了确定,我在 ngFor 循环之外插入了另一个 lottie-player 实例,以查看它会如何反应。控制这些 cards 的整个父组件只是将它们插入或弹出到数组中,每次插入后,我根据需要将 this.lottieANimDirection 更改为 1 或 -1,以便 ngOnChanges在 lottie 组件中可以获取更改并相应地渲染动画

我希望看到的是每个 lottie 都单独制作动画 - 但他们都这样做了。因此,如果我单击底部的那个,上面写着 'subscribe'...

上面的 lottie 也有动画效果。

不仅是上面卡片中的那个,还有用于测试目的的 <app-lottie-player> 我尝试在 ngFor 循环上方插入动画。我相信错误的原因可能是因为我构建这个基本组件的方式,而不是在控制器中创建播放器的一个实例,它正在重新更新同一个实例,导致接收到的信息显示在 DOM 中的所有插入中,因为它们实际上都来自一个实例。但是我不知道怎么解决。

如果有人对为什么彩票中会出现这个错误有丝毫的想法,我就是 ears/eyes。

我整理了一个 stackblitz,展示了我认为你的问题所在,即所有组件都传递了相同的状态实例。

https://stackblitz.com/edit/angular-ivy-lfckkn

div 的第一个数组在父级中都只有一个状态,因此在一个中切换它会更新所有状态。

app-card中的第二个都是它们自己的组件,有自己的状态。请注意,由于我们仍在将全局状态作为输入传递,因此在内部更改它时不会更新其他状态,但如果它在父级更新,它将把新值推送给所有子级。

子组件示例

import { Component, OnInit, Input} from '@angular/core';

@Component({
  selector: 'app-card',
  templateUrl: './card.component.html',
  styleUrls: ['./card.component.css']
})
export class CardComponent implements OnInit {
  @Input() card: {id: number, title: string};
  internalState = true;
  @Input() stateFromParent: boolean;
  constructor() { }

  ngOnInit() {
  }

  toggleInternalState() {
    this.internalState  = !this.internalState;
    this.stateFromParent = !this.stateFromParent;
  }

}

子组件模板


<div style="border: 1px solid green; padding: 5px;">
<p>ID: {{card.id}}</p>
<p>Title: {{card.title}}</p>
<p>internalState: {{internalState | json}}</p>
<p>stateFromParent: {{stateFromParent | json}} (NOTE: it will mange its own internal one, but if the parent's input changes that new value will be passed in)</p>
<button (click)="toggleInternalState()">Toggle Value</button>
</div>