I created the following Carousel using Angular Animations:
I created 5 variables defining the position of each state:
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.
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