mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
docs(animations): improve aio animations guides info around :enter/:leave (#44550)
improve the aio animations guide information around elements entering and leaving, this includes, querying them, their transitions and also improvements to the animations guide live examples PR Close #44550
This commit is contained in:
parent
e872e9d3a2
commit
569a262dfb
10 changed files with 283 additions and 28 deletions
|
|
@ -8,6 +8,7 @@ import * as auto from './auto.po';
|
|||
import * as filterStagger from './filter-stagger.po';
|
||||
import * as heroGroups from './hero-groups';
|
||||
import { getLinkById, sleepFor } from './util';
|
||||
import { getComponentSection, getToggleButton } from './querying.po';
|
||||
|
||||
describe('Animation Tests', () => {
|
||||
const openCloseHref = getLinkById('open-close');
|
||||
|
|
@ -17,6 +18,7 @@ describe('Animation Tests', () => {
|
|||
const autoHref = getLinkById('auto');
|
||||
const filterHref = getLinkById('heroes');
|
||||
const heroGroupsHref = getLinkById('hero-groups');
|
||||
const queryingHref = getLinkById('querying');
|
||||
|
||||
beforeAll(() => browser.get(''));
|
||||
|
||||
|
|
@ -218,7 +220,7 @@ describe('Animation Tests', () => {
|
|||
describe('Hero Groups Component', () => {
|
||||
beforeAll(async () => {
|
||||
await heroGroupsHref.click();
|
||||
await sleepFor(300);
|
||||
await sleepFor(400);
|
||||
});
|
||||
|
||||
it('should attach a flyInOut trigger to the list of items', async () => {
|
||||
|
|
@ -242,4 +244,49 @@ describe('Animation Tests', () => {
|
|||
await browser.wait(async () => await heroesList.count() < total, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Querying Component', () => {
|
||||
const queryingAnimationDuration = 2500;
|
||||
|
||||
beforeAll(async () => {
|
||||
await queryingHref.click();
|
||||
await sleepFor(queryingAnimationDuration);
|
||||
});
|
||||
|
||||
it('should toggle the section', async () => {
|
||||
const toggleButton = getToggleButton();
|
||||
const section = getComponentSection();
|
||||
|
||||
expect(await section.isPresent()).toBe(true);
|
||||
|
||||
// toggling off
|
||||
await toggleButton.click();
|
||||
await sleepFor(queryingAnimationDuration);
|
||||
expect(await section.isPresent()).toBe(false);
|
||||
|
||||
// toggling on
|
||||
await toggleButton.click();
|
||||
await sleepFor(queryingAnimationDuration);
|
||||
expect(await section.isPresent()).toBe(true);
|
||||
await sleepFor(queryingAnimationDuration);
|
||||
});
|
||||
|
||||
it(`should disable the button for the animation's duration`, async () => {
|
||||
const toggleButton = getToggleButton();
|
||||
expect(await toggleButton.isEnabled()).toBe(true);
|
||||
|
||||
// toggling off
|
||||
await toggleButton.click();
|
||||
expect(await toggleButton.isEnabled()).toBe(false);
|
||||
await sleepFor(queryingAnimationDuration);
|
||||
expect(await toggleButton.isEnabled()).toBe(true);
|
||||
|
||||
// toggling on
|
||||
await toggleButton.click();
|
||||
expect(await toggleButton.isEnabled()).toBe(false);
|
||||
await sleepFor(queryingAnimationDuration);
|
||||
expect(await toggleButton.isEnabled()).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
|
|||
16
aio/content/examples/animations/e2e/src/querying.po.ts
Normal file
16
aio/content/examples/animations/e2e/src/querying.po.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { by } from 'protractor';
|
||||
import { locate } from './util';
|
||||
|
||||
export function getComponent() {
|
||||
return by.css('app-querying');
|
||||
}
|
||||
|
||||
export function getToggleButton() {
|
||||
const toggleButton = () => by.className('toggle');
|
||||
return locate(getComponent(), toggleButton());
|
||||
}
|
||||
|
||||
export function getComponentSection() {
|
||||
const findSection = () => by.css('section');
|
||||
return locate(getComponent(), findSection());
|
||||
}
|
||||
|
|
@ -39,11 +39,11 @@ export const slideInAnimation =
|
|||
]),
|
||||
query(':enter', [
|
||||
animate('300ms ease-out', style({ left: '0%' }))
|
||||
])
|
||||
]),
|
||||
query('@*', animateChild())
|
||||
]),
|
||||
query(':enter', animateChild()),
|
||||
]),
|
||||
transition('* <=> FilterPage', [
|
||||
transition('* <=> *', [
|
||||
style({ position: 'relative' }),
|
||||
query(':enter, :leave', [
|
||||
style({
|
||||
|
|
@ -59,13 +59,13 @@ export const slideInAnimation =
|
|||
query(':leave', animateChild()),
|
||||
group([
|
||||
query(':leave', [
|
||||
animate('200ms ease-out', style({ left: '100%' }))
|
||||
animate('200ms ease-out', style({ left: '100%', opacity: 0 }))
|
||||
]),
|
||||
query(':enter', [
|
||||
animate('300ms ease-out', style({ left: '0%' }))
|
||||
])
|
||||
]),
|
||||
query('@*', animateChild())
|
||||
]),
|
||||
query(':enter', animateChild()),
|
||||
])
|
||||
// #enddocregion query
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,15 @@
|
|||
nav a {
|
||||
padding: .7rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: .3rem;
|
||||
}
|
||||
|
||||
form {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
nav {
|
||||
padding-bottom: 3rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
<a id="heroes" routerLink="/heroes" routerLinkActive="active">Filter/Stagger</a>
|
||||
<a id="hero-groups" routerLink="/hero-groups" routerLinkActive="active">Hero Groups</a>
|
||||
<a id="insert-remove" routerLink="/insert-remove" routerLinkActive="active">Insert/Remove</a>
|
||||
|
||||
<a id="querying" routerLink="/querying" routerLinkActive="active">Querying</a>
|
||||
</nav>
|
||||
|
||||
<!-- #docregion route-animations-outlet -->
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { HeroListAutoComponent } from './hero-list-auto.component';
|
|||
import { HomeComponent } from './home.component';
|
||||
import { AboutComponent } from './about.component';
|
||||
import { InsertRemoveComponent } from './insert-remove.component';
|
||||
import { QueryingComponent } from './querying.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
|
|
@ -28,17 +29,61 @@ import { InsertRemoveComponent } from './insert-remove.component';
|
|||
BrowserAnimationsModule,
|
||||
RouterModule.forRoot([
|
||||
{ path: '', pathMatch: 'full', redirectTo: '/enter-leave' },
|
||||
{ path: 'open-close', component: OpenClosePageComponent },
|
||||
{ path: 'status', component: StatusSliderPageComponent },
|
||||
{ path: 'toggle', component: ToggleAnimationsPageComponent },
|
||||
{ path: 'heroes', component: HeroListPageComponent,
|
||||
data: { animation: 'FilterPage' } },
|
||||
{ path: 'hero-groups', component: HeroListGroupPageComponent },
|
||||
{ path: 'enter-leave', component: HeroListEnterLeavePageComponent },
|
||||
{ path: 'auto', component: HeroListAutoCalcPageComponent },
|
||||
{ path: 'insert-remove', component: InsertRemoveComponent},
|
||||
{ path: 'home', component: HomeComponent, data: { animation: 'HomePage' } },
|
||||
{ path: 'about', component: AboutComponent, data: { animation: 'AboutPage' } },
|
||||
{
|
||||
path: 'open-close',
|
||||
component: OpenClosePageComponent,
|
||||
data: { animation: 'openClosePage' }
|
||||
},
|
||||
{
|
||||
path: 'status',
|
||||
component: StatusSliderPageComponent,
|
||||
data: { animation: 'statusPage' }
|
||||
},
|
||||
{
|
||||
path: 'toggle',
|
||||
component: ToggleAnimationsPageComponent,
|
||||
data: { animation: 'togglePage' }
|
||||
},
|
||||
{
|
||||
path: 'heroes',
|
||||
component: HeroListPageComponent,
|
||||
data: { animation: 'filterPage' }
|
||||
},
|
||||
{
|
||||
path: 'hero-groups',
|
||||
component: HeroListGroupPageComponent,
|
||||
data: { animation: 'heroGroupPage' }
|
||||
},
|
||||
{
|
||||
path: 'enter-leave',
|
||||
component: HeroListEnterLeavePageComponent,
|
||||
data: { animation: 'enterLeavePage' }
|
||||
},
|
||||
{
|
||||
path: 'auto',
|
||||
component: HeroListAutoCalcPageComponent,
|
||||
data: { animation: 'autoPage' }
|
||||
},
|
||||
{
|
||||
path: 'insert-remove',
|
||||
component: InsertRemoveComponent,
|
||||
data: { animation: 'insertRemovePage' }
|
||||
},
|
||||
{
|
||||
path: 'querying',
|
||||
component: QueryingComponent,
|
||||
data: { animation: 'queryingPage' }
|
||||
},
|
||||
{
|
||||
path: 'home',
|
||||
component: HomeComponent,
|
||||
data: { animation: 'HomePage' }
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
component: AboutComponent,
|
||||
data: { animation: 'AboutPage' }
|
||||
},
|
||||
])
|
||||
],
|
||||
// #enddocregion route-animation-data
|
||||
|
|
@ -59,6 +104,7 @@ import { InsertRemoveComponent } from './insert-remove.component';
|
|||
HeroListAutoComponent,
|
||||
HomeComponent,
|
||||
InsertRemoveComponent,
|
||||
QueryingComponent,
|
||||
AboutComponent
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
section {
|
||||
border: 1px solid black;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
section > * {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.hero {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 4px;
|
||||
color: black;
|
||||
background-color: #DDD;
|
||||
}
|
||||
|
||||
.hero .badge {
|
||||
display: inline-block;
|
||||
font-size: small;
|
||||
color: white;
|
||||
padding: 0.5rem;
|
||||
background-color: #3d5157;
|
||||
margin-right: .8em;
|
||||
border-radius: 4px 0 0 4px;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.hero .name {
|
||||
height: min-content;
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
import {
|
||||
Component,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
trigger,
|
||||
style,
|
||||
animate,
|
||||
transition,
|
||||
group,
|
||||
query,
|
||||
animateChild,
|
||||
keyframes
|
||||
} from '@angular/animations';
|
||||
|
||||
import { HEROES } from './mock-heroes';
|
||||
|
||||
@Component({
|
||||
selector: 'app-querying',
|
||||
template: `
|
||||
<nav>
|
||||
<button class="toggle" (click)="show = !show" [disabled]="toggleDisabled">Toggle View</button>
|
||||
</nav>
|
||||
<section *ngIf="show" @query (@query.start)="toggleDisabled = true" (@query.done)="toggleDisabled = false">
|
||||
<p>I am a simple child element</p>
|
||||
<p *ngIf="show">I am a child element that enters and leaves with its parent</p>
|
||||
<p @animateMe>I am a child element with an animation trigger</p>
|
||||
<div class="hero">
|
||||
<span class="badge">{{ hero.id }}</span>
|
||||
<span class="name">{{ hero.name }} <small>(heroes are always animated!)</small></span>
|
||||
</div>
|
||||
</section>
|
||||
`,
|
||||
styleUrls: ['./querying.component.css'],
|
||||
animations: [
|
||||
trigger('query', [
|
||||
transition(':enter', [
|
||||
style({ height: 0 }),
|
||||
group([
|
||||
animate(500, style({ height: '*' })),
|
||||
query(':enter', [
|
||||
style({ opacity: 0, transform: 'scale(0)'}),
|
||||
animate(2000, style({ opacity: 1, transform: 'scale(1)' }))
|
||||
]),
|
||||
query('.hero', [
|
||||
style({ transform: 'translateX(-100%)'}),
|
||||
animate('.7s 500ms ease-in', style({ transform: 'translateX(0)' }))
|
||||
]),
|
||||
]),
|
||||
query('@animateMe', animateChild()),
|
||||
]),
|
||||
transition(':leave', [
|
||||
style({ height: '*' }),
|
||||
query('@animateMe', animateChild()),
|
||||
group([
|
||||
animate('500ms 500ms', style({ height: '0', padding: '0' })),
|
||||
query(':leave', [
|
||||
style({ opacity: 1, transform: 'scale(1)'}),
|
||||
animate('1s', style({ opacity: 0, transform: 'scale(0)' }))
|
||||
]),
|
||||
query('.hero', [
|
||||
style({ transform: 'translateX(0)'}),
|
||||
animate('.7s ease-out', style({ transform: 'translateX(-100%)' }))
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
trigger('animateMe', [
|
||||
transition('* <=> *', animate('500ms cubic-bezier(.68,-0.73,.26,1.65)', keyframes([
|
||||
style({ backgroundColor: "transparent", color: '*', offset: 0 }),
|
||||
style({ backgroundColor: "blue", color: 'white', offset: 0.2 }),
|
||||
style({ backgroundColor: "transparent", color: '*', offset: 1 })
|
||||
])))
|
||||
]),
|
||||
]
|
||||
})
|
||||
export class QueryingComponent {
|
||||
toggleDisabled = false;
|
||||
show = true;
|
||||
hero = HEROES[0];
|
||||
}
|
||||
|
|
@ -20,11 +20,34 @@ The functions that control complex animation sequences are:
|
|||
|
||||
{@a complex-sequence}
|
||||
|
||||
## The query() function
|
||||
|
||||
Most complex animations rely on the `query()` function to find child elements and apply animations to them, basic examples of such are:
|
||||
- `query()` followed by `animate()`
|
||||
used to query simple HTML elements and directly apply animations to them.
|
||||
- `query()` followed by `animateChild()`
|
||||
used to query child elements which themselves have animations metadata applied to them and trigger such animation (which would be otherwise be blocked by the current/parent element's animation)
|
||||
|
||||
The first argument of `query()` is a [css selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) string which can also contain the following angular-specific tokens:
|
||||
- `:enter`/`:leave` for entering/leaving elements
|
||||
- `:animating` for elements currently animating
|
||||
- `@*`/`@triggerName` for elements with any or a specific trigger
|
||||
- `:self` the animating element itself
|
||||
|
||||
<div class="callout is-helpful">
|
||||
|
||||
<header>Entering and Leaving Elements</header>
|
||||
|
||||
Not all child elements are actually considered as entering/leaving, this can at times be counterintuitive and confusing, please see the [query api docs](/api/animations/query#entering-and-leaving-elements) for more information.
|
||||
|
||||
|
||||
You can also see an illustration of this in the animations live example (introduced in the animations [introduction section](/guide/animations#about-this-guide)) under the Querying tab.
|
||||
|
||||
</div>
|
||||
|
||||
## Animate multiple elements using query() and stagger() functions
|
||||
|
||||
The `query()` function lets you find inner elements within the element that is being animated. This function targets specific HTML elements within a parent component and applies animations to each element individually. Angular intelligently handles setup, teardown, and cleanup as it coordinates the elements across the page.
|
||||
|
||||
The `stagger()` function lets you define a timing gap between each queried item that is animated and thus animates elements with a delay between them.
|
||||
After having queried child elements via `query()`, the `stagger()` function lets you define a timing gap between each queried item that is animated and thus animates elements with a delay between them.
|
||||
|
||||
The following example demonstrates how to use the `query()` and `stagger()` functions to animate a list (of heroes) adding each in sequence, with a slight delay, from top to bottom.
|
||||
|
||||
|
|
|
|||
|
|
@ -75,12 +75,6 @@ Combine wildcard and void states in a transition to trigger animations that ente
|
|||
|
||||
This section shows how to animate elements entering or leaving a page.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**Note:** For this example, an element entering or leaving a view is equivalent to being inserted or removed from the DOM.
|
||||
|
||||
</div>
|
||||
|
||||
Add a new behavior:
|
||||
|
||||
* When you add a hero to the list of heroes, it appears to fly onto the page from the left.
|
||||
|
|
@ -109,6 +103,12 @@ So, use the aliases `:enter` and `:leave` to target HTML elements that are inser
|
|||
|
||||
The `:enter` transition runs when any `*ngIf` or `*ngFor` views are placed on the page, and `:leave` runs when those views are removed from the page.
|
||||
|
||||
<div class="alert is-important">
|
||||
|
||||
**Note:** Entering/leaving behaviors can sometime be confusing. As a rule of thumb consider that any element being added to the DOM by Angular passes via the `:enter` transition, but only elements being directly removed from the DOM by Angular pass via the `:leave` transition (e.g. an element's view is removed from the DOM because its parent is being removed from the DOM or the app's route has changed, then the element will not pass via the `:leave` transition).
|
||||
|
||||
</div>
|
||||
|
||||
This example has a special trigger for the enter and leave animation called `myInsertRemoveTrigger`. The HTML template contains the following code.
|
||||
|
||||
<code-example path="animations/src/app/insert-remove.component.html" header="src/app/insert-remove.component.html" region="insert-remove" language="typescript">
|
||||
|
|
|
|||
Loading…
Reference in a new issue