I have created a Layout Manager in Angular which can take in Components and then Display it in View and add animations to it while each component is shown in View and Goes out of view .
At a single instance of time either one panel or max of two panels can be shown in view .
This is the Stackblitz link
to the Same the problem with this is the transitions are not smooth and also it does appear as streamlined as it should be the design is as follows .
Now what i am trying to achieve is when the app is loaded 1-2 are shown by default but as i change the panels the transitions change like for eg
1-3 as 2 is moving out of view it should slide left and easeout and 3 should should in and ease out. and then if from 1-3 we go to 2-3 1 should ease out to the right and 2 should slide in .
Also the panels can take some percentages(33 %, 66% or 100% ) of the screen width .
I am not sure if i am able to explain it properly I have been stuck on this for weeks with transitions if anyone can help it will be awesome, Thanks
Thanks to Saddam who helped create this Animation this is exactly what i want from animations - https://imgur.com/a/qZ3vtDb this is for visual purpose only
In Angular, transition states can be defined explicitly through the state() function, or using the predefined * (wildcard) and void states. Wildcard state. An asterisk * or wildcard matches any animation state. This is useful for defining transitions that apply regardless of the HTML element's start or end state.
Angular's animation system is built on CSS functionality, which means you can animate any property that the browser considers animatable. This includes positions, sizes, transforms, colors, borders, and more. The W3C maintains a list of animatable properties on its CSS Transitions page.
Each time an animation is triggered in Angular, the parent animation always gets priority and child animations are blocked. For a child animation to run, the parent animation must query each of the elements containing child animations. It then lets the animations run using the animateChild() function.
I have changed the PanelComponent
in your StackBlitz sample to work in the way the provided animation is showing.
You need just three states. One when the component is initially outside on the right. From there it moves into view this is the second state. After that it moves out of view to the left the third state. Once it is out of view to the left you move it back to the right initial state so it can come back in when needed.
Here is the code for the changed panel component:
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';
}
}
}
As you see I have split the setStyle
into two methods setInViewStyle
and moveOut
. The setInViewStyle
sets the panel style and moves it into view. The moveOut
method moves the panel out of view to the left. Because of this I have also changed the layout manager method panelTransformation
.
Here is the changed code:
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;
}
}
As you can see I have completely changed the logic so I first move out panels that have to be removed wait for the animation to finish and then move in the new panels. Here is a link to my StackBlitz sample that implements all this so you can also see it working.
As requested in the comment I provide also another sample with moving in both directions. This makes things more complicated. I had to add one more transition for the move in the other direction. And added the possibility to set the direction in the moveOut
method. In my opinion it does not look so good because it can have some flicker.
The new panel component code:
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';
}
}
In the panelTransformation
method I added the logic to set the direction for moving out to right if it is the first panel.
This is the updated code:
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;
}
}
And also the StackBlitz sample for this implementation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With