Search code examples
angularangular-animations

Not able to set Carousel properly using Angular Animations


I created the following Carousel using Angular Animations:

enter image description here

I created 5 variables defining the position of each state:

enter image description here

enter image description here

But when I try to add more data, some items simply goes out of the animation (as 'Joe' and 'Hidde' in above picture).

I am trying to achieve the following:

(i) If data is more, I need the animation to be continued (coming from nowhere into the DOM from right side, I guess we can do that using ':enter' and ':leave' aliases but not able to apply them here.)

(ii) Let's say if there are only two items, then items should animate in between the 'current' and 'next' states but not going to the far right.

Here's the Stackblitz link.

And here's the code:

.ts:

animations: [
    trigger("carousel", [
      state(
        "current",
        style({
          position: "absolute",
          top: "16%",
          right: "54%",
          bottom: "39%",
          left: "40%"
        })
      ),
      state(
        "next",
        style({
          position: "absolute",
          top: "51%",
          right: "77%",
          bottom: "calc(2% + 3px)",
          left: "calc(1% + 6px)"
        })
      ),
      state(
        "futureNext",
        style({
          position: "absolute",
          top: "51%",
          right: "54%",
          bottom: "calc(2% + 3px)",
          left: "calc(26% + 5px)"
        })
      ),
      state(
        "postFutureNext",
        style({
          position: "absolute",
          top: "51%",
          right: "31%",
          bottom: "calc(2% + 3px)",
          left: "calc(51% + 5px)"
        })
      ),
      state(
        "nextToPostFutureNext",
        style({
          position: "absolute",
          top: "51%",
          right: "8%",
          bottom: "calc(2% + 3px)",
          left: "calc(76% + 4px)"
        })
      ),
      transition(
        "current <=> nextToPostFutureNext",
        animate("2000ms ease-in-out")
      ),
      transition(
        "nextToPostFutureNext <=> postFutureNext",
        animate("2000ms ease-in-out")
      ),
      transition(
        "postFutureNext <=> futureNext",
        animate("2000ms ease-in-out")
      ),
      transition("futureNext <=> next", animate("2000ms ease-in-out")),
      transition("next <=> current", animate("2000ms ease-in-out"))
    ])
  ]
    clearTimeOutVar: any;
      current = 0;
      next = 1;
      futureNext = 2;
      postFutureNext = 3;
      nextToPostFutureNext = 4;

  ngOnInit() {
    this.runSlideShow();
  }

  runSlideShow() {
    this.clearTimeOutVar = setTimeout(() => {
      this.updateAnimationVariables();
      this.runSlideShow();
    }, 3000);
  }

  updateAnimationVariables() {
    if (this.current === 4) {
      this.current = 0;
    } else if (this.current < 4) {
      this.current++;
    }

    if (this.next === 4) {
      this.next = 0;
    } else if (this.next < 4) {
      this.next++;
    }

    if (this.futureNext === 4) {
      this.futureNext = 0;
    } else if (this.futureNext < 4) {
      this.futureNext++;
    }

    if (this.postFutureNext === 4) {
      this.postFutureNext = 0;
    } else if (this.postFutureNext < 4) {
      this.postFutureNext++;
    }

    if (this.nextToPostFutureNext === 4) {
      this.nextToPostFutureNext = 0;
    } else if (this.nextToPostFutureNext < 4) {
      this.nextToPostFutureNext++;
    }
  }

  ngOnDestroy() {
    clearTimeout(this.clearTimeOutVar);
  }

.html:

<div class="container">
    <div class="header">
        A Simple Carousel
    </div>
    <div class="data">
        <div class="cards" *ngFor="let item of data; let i = index;" [@carousel]="
        i == current ? 'current' :
        i == next ? 'next' : 
        i == futureNext ? 'futureNext' : 
        i == postFutureNext ? 'postFutureNext' : 
        i == nextToPostFutureNext ? 'nextToPostFutureNext' : ''
      ">
            <div class="name">{{ item.name }}</div>
            <div class="age">{{ item.age }}</div>
        </div>
    </div>
</div>

Please let me know if I am not able to explain any point in my approach.

Thanks.


Solution

  • It's difficult for me imagine what kind of animation you want to do. BTW (I expect don't confusse you), You can create animations only by code. Some like this SO

    The idea is in constructor inject the Animation builder

      constructor(private builder: AnimationBuilder) {}
    

    After it's always the same, we get the elements to animate using ViewChildren and others elements using ViewChild

      @ViewChildren("card") items: QueryList<ElementRef>;
      @ViewChild("container") container: ElementRef;
      @ViewChild("dataInit") dataInit: ElementRef;
    

    We create a function to calculate auxiliars variables that allow us calculate the position final of the items

      calculateDimensions() {
        this.rectElement = this.items.first.nativeElement.getBoundingClientRect();
        const rect = this.dataInit.nativeElement.getBoundingClientRect();
        const rectContainer = this.container.nativeElement.getBoundingClientRect();
    
        this.rect = {};
        this.rect.height = rectContainer.height - rect.y;
        this.rect.y = rect.y;
        this.rect.width = rectContainer.width - this.rectElement.width;
      }
    

    And in ngAfterViewInit call to this function and to a function to animated

      ngAfterViewInit() {
        this.calculateDimensions();
        this.animateCarousel();
      }
    

    Let's go with the function animateCarousel

      animateCarousel() {
        //with each element in ViewChildren
        this.items.forEach((item: ElementRef, index: number) => {
    
          //I calcule the "order" to change the z-index of the element
          this.order[index] =this.items.length-
            ((index + this.selectedIndex) % this.items.length);
    
          //Calcule the style final
          const itemStyle = this.getStyle2(index);
    
          //create an animation using this.builder.build
          const myAnimation = this.builder.build([
            animate(this.timing, style(itemStyle))
          ]);
    
          //Use a variable "player" declared: private player: AnimationPlayer;
          //and the animation.create over the "nativeElement"
          this.player = myAnimation.create(item.nativeElement);
    
          //As we want an infinite carousel, with one element (I choose the first)
          if (index == 0)
            //when finish, 
            this.player.onDone(() => {
              this.calculateDimensions(); //recalcule the dimensions
                                          //this makes that if resize the navigator
                                          //the animation works well
                      
              //add or substract the "selectedIndex" using % operator
              //to always gets values from 0 to this.items.length-1
              this.selectedIndex =
                (this.selectedIndex + this.items.length - 1) % this.items.length;
    
              //and call to the function again
              this.animateCarousel();
            });
    
           //Finally say player.play()
          this.player.play();
        });
      }
    

    Well, the funcion getStyles can be how we want, but always has the way

      getStyle(index: number) {
        //get the "pos" that it's always
        const pos = (index + this.selectedIndex) % this.items.length;
    
        //now we return the style we want, e.g.
        if (pos == 0)
          return { top: this.rect.y + "px", left: this.rect.width / 2 + "px" };
    
        return {
          top: this.rect.height - this.rectElement.height / 2 + "px",
          left: pos * (this.rect.width / this.items.length) + "px"
        };
      }
    

    The .html

    <div #container class="container">
    
        <div class="header">
            A Simple Carousel
        </div>
        <!--this div it's only to situate the carousel-->
        <div #dataInit class="data"></div>
    
        <!--see how change the "z-index"
        <div #card class="cards" [ngStyle]="{'z-index':order[i]}" 
           *ngFor="let item of data; let i = index;">
                <div class="name">{{ item.name }}</div>
                <div class="age">{{ item.age }}</div>
        </div>
    </div>
    

    You can see the final result