Angular 中的转换
Transitions in Angular
我在 Angular 中创建了一个布局管理器,它可以接收组件,然后在视图中显示它,并在每个组件在视图中显示和离开视图时向其添加动画。
在单个时间实例中,可以在视图中显示一个面板或最多两个面板。
这是 Stackblitz link
与此相同的问题是过渡不平滑,而且它确实看起来很流线型,设计如下。
现在我想要实现的是当应用程序加载时默认显示 1-2,但是当我更改面板时,转换会发生变化,例如
1-3 当 2 移出视图时,它应该向左滑动并缓出,而 3 应该进入并缓出。然后如果从 1-3 我们转到 2-3 1 应该向右缓和而 2 应该滑入。
面板也可以占据屏幕宽度的某些百分比(33 %、66% 或 100%)。
我不确定我是否能够正确解释它我已经坚持了几个星期的过渡,如果有人可以帮助它会很棒,谢谢
感谢帮助创建此动画的 Saddam 这正是我想要的动画 - https://imgur.com/a/qZ3vtDb 这仅用于视觉目的
我已将您的 StackBlitz 示例中的 PanelComponent
更改为以提供的动画显示方式工作。
你只需要三个状态。一个组件最初位于右侧的外部。从那里它进入视野,这是第二种状态。之后它移出视野,向左移动到第三种状态。一旦它离开左侧的视野,您就可以将其移回右侧的初始状态,以便在需要时返回。
下面是更改面板组件的代码:
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';
}
}
}
如您所见,我已将 setStyle
拆分为两种方法 setInViewStyle
和 moveOut
。 setInViewStyle
设置面板样式并将其移动到视图中。 moveOut
方法将面板移到左侧视图之外。因此,我还更改了布局管理器方法 panelTransformation
。
这是更改后的代码:
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;
}
}
如您所见,我已经完全改变了逻辑,所以我先移出必须移除的面板,等待动画完成,然后移入新面板。
这是我的 StackBlitz sample 的 link 实现了所有这些所以你也可以看到它的工作。
根据评论中的要求,我还提供了另一个双向移动的示例。这使事情变得更加复杂。我不得不为另一个方向的移动添加一个过渡。并添加了在 moveOut
方法中设置方向的可能性。在我看来,它看起来不太好,因为它可能会有一些闪烁。
新面板组件代码:
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';
}
}
在panelTransformation
方法中,我添加了逻辑来设置如果它是第一个面板向右移动的方向。
这是更新后的代码:
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;
}
}
还有此实现的 StackBlitz sample。
我在 Angular 中创建了一个布局管理器,它可以接收组件,然后在视图中显示它,并在每个组件在视图中显示和离开视图时向其添加动画。
在单个时间实例中,可以在视图中显示一个面板或最多两个面板。
这是 Stackblitz link
与此相同的问题是过渡不平滑,而且它确实看起来很流线型,设计如下。
现在我想要实现的是当应用程序加载时默认显示 1-2,但是当我更改面板时,转换会发生变化,例如
1-3 当 2 移出视图时,它应该向左滑动并缓出,而 3 应该进入并缓出。然后如果从 1-3 我们转到 2-3 1 应该向右缓和而 2 应该滑入。
面板也可以占据屏幕宽度的某些百分比(33 %、66% 或 100%)。
我不确定我是否能够正确解释它我已经坚持了几个星期的过渡,如果有人可以帮助它会很棒,谢谢
感谢帮助创建此动画的 Saddam 这正是我想要的动画 - https://imgur.com/a/qZ3vtDb 这仅用于视觉目的
我已将您的 StackBlitz 示例中的 PanelComponent
更改为以提供的动画显示方式工作。
你只需要三个状态。一个组件最初位于右侧的外部。从那里它进入视野,这是第二种状态。之后它移出视野,向左移动到第三种状态。一旦它离开左侧的视野,您就可以将其移回右侧的初始状态,以便在需要时返回。
下面是更改面板组件的代码:
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';
}
}
}
如您所见,我已将 setStyle
拆分为两种方法 setInViewStyle
和 moveOut
。 setInViewStyle
设置面板样式并将其移动到视图中。 moveOut
方法将面板移到左侧视图之外。因此,我还更改了布局管理器方法 panelTransformation
。
这是更改后的代码:
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;
}
}
如您所见,我已经完全改变了逻辑,所以我先移出必须移除的面板,等待动画完成,然后移入新面板。 这是我的 StackBlitz sample 的 link 实现了所有这些所以你也可以看到它的工作。
根据评论中的要求,我还提供了另一个双向移动的示例。这使事情变得更加复杂。我不得不为另一个方向的移动添加一个过渡。并添加了在 moveOut
方法中设置方向的可能性。在我看来,它看起来不太好,因为它可能会有一些闪烁。
新面板组件代码:
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';
}
}
在panelTransformation
方法中,我添加了逻辑来设置如果它是第一个面板向右移动的方向。
这是更新后的代码:
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;
}
}
还有此实现的 StackBlitz sample。