|
@ -1,351 +1,259 @@
|
|||
'use strict'; // necessary for es6 output in node
|
||||
|
||||
import { browser, element, by, ElementFinder } from 'protractor';
|
||||
import { logging, promise } from 'selenium-webdriver';
|
||||
import { browser } from 'protractor';
|
||||
import { logging } from 'selenium-webdriver';
|
||||
import * as openClose from './open-close.po';
|
||||
import * as statusSlider from './status-slider.po';
|
||||
import * as toggle from './toggle.po';
|
||||
import * as enterLeave from './enter-leave.po';
|
||||
import * as auto from './auto.po';
|
||||
import * as filterStagger from './filter-stagger.po';
|
||||
import * as heroGroups from './hero-groups';
|
||||
import { getLinkById, sleepFor } from './util';
|
||||
|
||||
/**
|
||||
* The tests here basically just checking that the end styles
|
||||
* of each animation are in effect.
|
||||
*
|
||||
* Relies on the Angular testability only becoming stable once
|
||||
* animation(s) have finished.
|
||||
*
|
||||
* Ideally we'd use https://developer.mozilla.org/en-US/docs/Web/API/Document/getAnimations
|
||||
* but they're not supported in Chrome at the moment. The upcoming nganimate polyfill
|
||||
* may also add some introspection support.
|
||||
*/
|
||||
describe('Animation Tests', () => {
|
||||
const openCloseHref = getLinkById('open-close');
|
||||
const statusSliderHref = getLinkById('status');
|
||||
const toggleHref = getLinkById('toggle');
|
||||
const enterLeaveHref = getLinkById('enter-leave');
|
||||
const autoHref = getLinkById('auto');
|
||||
const filterHref = getLinkById('heroes');
|
||||
const heroGroupsHref = getLinkById('hero-groups');
|
||||
|
||||
const INACTIVE_COLOR = 'rgba(238, 238, 238, 1)';
|
||||
const ACTIVE_COLOR = 'rgba(207, 216, 220, 1)';
|
||||
const NO_TRANSFORM_MATRIX_REGEX = /matrix\(1,\s*0,\s*0,\s*1,\s*0,\s*0\)/;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeAll(() => {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
describe('basic states', () => {
|
||||
describe('Open/Close Component', () => {
|
||||
|
||||
let host: ElementFinder;
|
||||
|
||||
beforeEach(() => {
|
||||
host = element(by.css('app-hero-list-basic'));
|
||||
beforeAll(async () => {
|
||||
await openCloseHref.click();
|
||||
sleepFor();
|
||||
});
|
||||
|
||||
it('animates between active and inactive', () => {
|
||||
addInactiveHero();
|
||||
it('should be open', async () => {
|
||||
let text = await openClose.getComponentText();
|
||||
const toggleButton = openClose.getToggleButton();
|
||||
const container = openClose.getComponentContainer();
|
||||
|
||||
let li = host.element(by.css('li'));
|
||||
if (text.includes('Closed')) {
|
||||
await toggleButton.click();
|
||||
sleepFor();
|
||||
}
|
||||
|
||||
expect(getScaleX(li)).toBe(1.0);
|
||||
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
|
||||
text = await openClose.getComponentText();
|
||||
const containerHeight = await container.getCssValue('height');
|
||||
|
||||
li.click();
|
||||
browser.driver.sleep(300);
|
||||
expect(getScaleX(li)).toBe(1.1);
|
||||
expect(li.getCssValue('backgroundColor')).toBe(ACTIVE_COLOR);
|
||||
|
||||
li.click();
|
||||
browser.driver.sleep(300);
|
||||
expect(getScaleX(li)).toBe(1.0);
|
||||
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
|
||||
expect(text).toContain('The box is now Open!');
|
||||
expect(containerHeight).toBe('200px');
|
||||
});
|
||||
|
||||
});
|
||||
it('should be closed', async () => {
|
||||
let text = await openClose.getComponentText();
|
||||
const toggleButton = openClose.getToggleButton();
|
||||
const container = openClose.getComponentContainer();
|
||||
|
||||
describe('styles inline in transitions', () => {
|
||||
if (text.includes('Open')) {
|
||||
await toggleButton.click();
|
||||
sleepFor();
|
||||
}
|
||||
|
||||
let host: ElementFinder;
|
||||
text = await openClose.getComponentText();
|
||||
const containerHeight = await container.getCssValue('height');
|
||||
|
||||
beforeEach(function() {
|
||||
host = element(by.css('app-hero-list-inline-styles'));
|
||||
expect(text).toContain('The box is now Closed!');
|
||||
expect(containerHeight).toBe('100px');
|
||||
});
|
||||
|
||||
it('are not kept after animation', () => {
|
||||
addInactiveHero();
|
||||
it('should log animation events', async () => {
|
||||
const toggleButton = openClose.getToggleButton();
|
||||
const loggingCheckbox = openClose.getLoggingCheckbox();
|
||||
await loggingCheckbox.click();
|
||||
await toggleButton.click();
|
||||
|
||||
let li = host.element(by.css('li'));
|
||||
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
|
||||
|
||||
li.click();
|
||||
browser.driver.sleep(300);
|
||||
expect(getScaleX(li)).toBe(1.0);
|
||||
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
|
||||
});
|
||||
const animationMessages = logs.filter(({ message }) => message.indexOf('Animation') !== -1 ? true : false);
|
||||
|
||||
});
|
||||
|
||||
describe('combined transition syntax', () => {
|
||||
|
||||
let host: ElementFinder;
|
||||
|
||||
beforeEach(() => {
|
||||
host = element(by.css('app-hero-list-combined-transitions'));
|
||||
});
|
||||
|
||||
it('animates between active and inactive', () => {
|
||||
addInactiveHero();
|
||||
|
||||
let li = host.element(by.css('li'));
|
||||
|
||||
expect(getScaleX(li)).toBe(1.0);
|
||||
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
|
||||
|
||||
li.click();
|
||||
browser.driver.sleep(300);
|
||||
expect(getScaleX(li)).toBe(1.1);
|
||||
expect(li.getCssValue('backgroundColor')).toBe(ACTIVE_COLOR);
|
||||
|
||||
li.click();
|
||||
browser.driver.sleep(300);
|
||||
expect(getScaleX(li)).toBe(1.0);
|
||||
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('two-way transition syntax', () => {
|
||||
|
||||
let host: ElementFinder;
|
||||
|
||||
beforeEach(() => {
|
||||
host = element(by.css('app-hero-list-twoway'));
|
||||
});
|
||||
|
||||
it('animates between active and inactive', () => {
|
||||
addInactiveHero();
|
||||
|
||||
let li = host.element(by.css('li'));
|
||||
|
||||
expect(getScaleX(li)).toBe(1.0);
|
||||
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
|
||||
|
||||
li.click();
|
||||
browser.driver.sleep(300);
|
||||
expect(getScaleX(li)).toBe(1.1);
|
||||
expect(li.getCssValue('backgroundColor')).toBe(ACTIVE_COLOR);
|
||||
|
||||
li.click();
|
||||
browser.driver.sleep(300);
|
||||
expect(getScaleX(li)).toBe(1.0);
|
||||
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('enter & leave', () => {
|
||||
|
||||
let host: ElementFinder;
|
||||
|
||||
beforeEach(() => {
|
||||
host = element(by.css('app-hero-list-enter-leave'));
|
||||
});
|
||||
|
||||
it('adds and removes element', () => {
|
||||
addInactiveHero();
|
||||
|
||||
let li = host.element(by.css('li'));
|
||||
expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX);
|
||||
|
||||
removeHero();
|
||||
expect(li.isPresent()).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('enter & leave & states', () => {
|
||||
|
||||
let host: ElementFinder;
|
||||
|
||||
beforeEach(function() {
|
||||
host = element(by.css('app-hero-list-enter-leave-states'));
|
||||
});
|
||||
|
||||
it('adds and removes and animates between active and inactive', () => {
|
||||
addInactiveHero();
|
||||
|
||||
let li = host.element(by.css('li'));
|
||||
|
||||
expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX);
|
||||
|
||||
li.click();
|
||||
browser.driver.sleep(300);
|
||||
expect(getScaleX(li)).toBe(1.1);
|
||||
|
||||
li.click();
|
||||
browser.driver.sleep(300);
|
||||
expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX);
|
||||
|
||||
removeHero();
|
||||
expect(li.isPresent()).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('auto style calc', () => {
|
||||
|
||||
let host: ElementFinder;
|
||||
|
||||
beforeEach(function() {
|
||||
host = element(by.css('app-hero-list-auto'));
|
||||
});
|
||||
|
||||
it('adds and removes element', () => {
|
||||
addInactiveHero();
|
||||
|
||||
let li = host.element(by.css('li'));
|
||||
expect(li.getCssValue('height')).toBe('50px');
|
||||
|
||||
removeHero();
|
||||
expect(li.isPresent()).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('different timings', () => {
|
||||
|
||||
let host: ElementFinder;
|
||||
|
||||
beforeEach(() => {
|
||||
host = element(by.css('app-hero-list-timings'));
|
||||
});
|
||||
|
||||
it('adds and removes element', () => {
|
||||
addInactiveHero();
|
||||
|
||||
let li = host.element(by.css('li'));
|
||||
expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX);
|
||||
expect(li.getCssValue('opacity')).toMatch('1');
|
||||
|
||||
removeHero();
|
||||
expect(li.isPresent()).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('multiple keyframes', () => {
|
||||
|
||||
let host: ElementFinder;
|
||||
|
||||
beforeEach(() => {
|
||||
host = element(by.css('app-hero-list-multistep'));
|
||||
});
|
||||
|
||||
it('adds and removes element', () => {
|
||||
addInactiveHero();
|
||||
|
||||
let li = host.element(by.css('li'));
|
||||
expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX);
|
||||
expect(li.getCssValue('opacity')).toMatch('1');
|
||||
|
||||
removeHero();
|
||||
expect(li.isPresent()).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('parallel groups', () => {
|
||||
|
||||
let host: ElementFinder;
|
||||
|
||||
beforeEach(() => {
|
||||
host = element(by.css('app-hero-list-groups'));
|
||||
});
|
||||
|
||||
it('adds and removes element', () => {
|
||||
addInactiveHero();
|
||||
|
||||
let li = host.element(by.css('li'));
|
||||
expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX);
|
||||
expect(li.getCssValue('opacity')).toMatch('1');
|
||||
|
||||
removeHero(700);
|
||||
expect(li.isPresent()).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('adding active heroes', () => {
|
||||
|
||||
let host: ElementFinder;
|
||||
|
||||
beforeEach(() => {
|
||||
host = element(by.css('app-hero-list-basic'));
|
||||
});
|
||||
|
||||
it('animates between active and inactive', () => {
|
||||
addActiveHero();
|
||||
|
||||
let li = host.element(by.css('li'));
|
||||
|
||||
expect(getScaleX(li)).toBe(1.1);
|
||||
expect(li.getCssValue('backgroundColor')).toBe(ACTIVE_COLOR);
|
||||
|
||||
li.click();
|
||||
browser.driver.sleep(300);
|
||||
expect(getScaleX(li)).toBe(1.0);
|
||||
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
|
||||
|
||||
li.click();
|
||||
browser.driver.sleep(300);
|
||||
expect(getScaleX(li)).toBe(1.1);
|
||||
expect(li.getCssValue('backgroundColor')).toBe(ACTIVE_COLOR);
|
||||
expect(animationMessages.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('callbacks', () => {
|
||||
it('fires a callback on start and done', () => {
|
||||
addActiveHero();
|
||||
browser.manage().logs().get(logging.Type.BROWSER)
|
||||
.then((logs: logging.Entry[]) => {
|
||||
const animationMessages = logs.filter((log) => {
|
||||
return log.message.indexOf('Animation') !== -1 ? true : false;
|
||||
});
|
||||
describe('Status Slider Component', () => {
|
||||
const activeColor = 'rgba(255, 165, 0, 1)';
|
||||
const inactiveColor = 'rgba(0, 0, 255, 1)';
|
||||
|
||||
expect(animationMessages.length).toBeGreaterThan(0);
|
||||
});
|
||||
beforeAll(async () => {
|
||||
await statusSliderHref.click();
|
||||
sleepFor(2000);
|
||||
});
|
||||
|
||||
it('should be inactive with an orange background', async () => {
|
||||
let text = await statusSlider.getComponentText();
|
||||
const toggleButton = statusSlider.getToggleButton();
|
||||
const container = statusSlider.getComponentContainer();
|
||||
|
||||
if (text === 'Active') {
|
||||
await toggleButton.click();
|
||||
sleepFor(2000);
|
||||
}
|
||||
|
||||
text = await statusSlider.getComponentText();
|
||||
const bgColor = await container.getCssValue('backgroundColor');
|
||||
|
||||
expect(text).toBe('Inactive');
|
||||
expect(bgColor).toBe(inactiveColor);
|
||||
});
|
||||
|
||||
it('should be active with a blue background', async () => {
|
||||
let text = await statusSlider.getComponentText();
|
||||
const toggleButton = statusSlider.getToggleButton();
|
||||
const container = statusSlider.getComponentContainer();
|
||||
|
||||
if (text === 'Inactive') {
|
||||
await toggleButton.click();
|
||||
sleepFor(2000);
|
||||
}
|
||||
|
||||
text = await statusSlider.getComponentText();
|
||||
const bgColor = await container.getCssValue('backgroundColor');
|
||||
|
||||
expect(text).toBe('Active');
|
||||
expect(bgColor).toBe(activeColor);
|
||||
});
|
||||
});
|
||||
|
||||
function addActiveHero(sleep?: number) {
|
||||
sleep = sleep || 500;
|
||||
element(by.buttonText('Add active hero')).click();
|
||||
browser.driver.sleep(sleep);
|
||||
}
|
||||
|
||||
function addInactiveHero(sleep?: number) {
|
||||
sleep = sleep || 500;
|
||||
element(by.buttonText('Add inactive hero')).click();
|
||||
browser.driver.sleep(sleep);
|
||||
}
|
||||
|
||||
function removeHero(sleep?: number) {
|
||||
sleep = sleep || 500;
|
||||
element(by.buttonText('Remove hero')).click();
|
||||
browser.driver.sleep(sleep);
|
||||
}
|
||||
|
||||
function getScaleX(el: ElementFinder) {
|
||||
return Promise.all([
|
||||
getBoundingClientWidth(el),
|
||||
getOffsetWidth(el)
|
||||
]).then(function(promiseResolutions) {
|
||||
let clientWidth = promiseResolutions[0];
|
||||
let offsetWidth = promiseResolutions[1];
|
||||
return clientWidth / offsetWidth;
|
||||
describe('Toggle Animations Component', () => {
|
||||
beforeAll(async () => {
|
||||
await toggleHref.click();
|
||||
sleepFor();
|
||||
});
|
||||
}
|
||||
|
||||
function getBoundingClientWidth(el: ElementFinder) {
|
||||
return browser.executeScript(
|
||||
'return arguments[0].getBoundingClientRect().width',
|
||||
el.getWebElement()
|
||||
) as PromiseLike<number>;
|
||||
}
|
||||
it('should disabled animations on the child element', async () => {
|
||||
const toggleButton = toggle.getToggleAnimationsButton();
|
||||
|
||||
function getOffsetWidth(el: ElementFinder) {
|
||||
return browser.executeScript(
|
||||
'return arguments[0].offsetWidth',
|
||||
el.getWebElement()
|
||||
) as PromiseLike<number>;
|
||||
}
|
||||
await toggleButton.click();
|
||||
|
||||
const container = toggle.getComponentContainer();
|
||||
const cssClasses = await container.getAttribute('class');
|
||||
|
||||
expect(cssClasses).toContain('ng-animate-disabled');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Enter/Leave Component', () => {
|
||||
beforeAll(async () => {
|
||||
await enterLeaveHref.click();
|
||||
sleepFor(100);
|
||||
});
|
||||
|
||||
it('should attach a flyInOut trigger to the list of items', async () => {
|
||||
const heroesList = enterLeave.getHeroesList();
|
||||
const hero = heroesList.get(0);
|
||||
const cssClasses = await hero.getAttribute('class');
|
||||
const transform = await hero.getCssValue('transform');
|
||||
|
||||
expect(cssClasses).toContain('ng-trigger-flyInOut');
|
||||
expect(transform).toBe('matrix(1, 0, 0, 1, 0, 0)');
|
||||
});
|
||||
|
||||
it('should remove the hero from the list when clicked', async () => {
|
||||
const heroesList = enterLeave.getHeroesList();
|
||||
const total = await heroesList.count();
|
||||
const hero = heroesList.get(0);
|
||||
|
||||
await hero.click();
|
||||
await sleepFor(100);
|
||||
const newTotal = await heroesList.count();
|
||||
|
||||
expect(newTotal).toBeLessThan(total);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Auto Calculation Component', () => {
|
||||
beforeAll(async () => {
|
||||
await autoHref.click();
|
||||
sleepFor(0);
|
||||
});
|
||||
|
||||
it('should attach a shrinkOut trigger to the list of items', async () => {
|
||||
const heroesList = auto.getHeroesList();
|
||||
const hero = heroesList.get(0);
|
||||
const cssClasses = await hero.getAttribute('class');
|
||||
|
||||
expect(cssClasses).toContain('ng-trigger-shrinkOut');
|
||||
});
|
||||
|
||||
it('should remove the hero from the list when clicked', async () => {
|
||||
const heroesList = auto.getHeroesList();
|
||||
const total = await heroesList.count();
|
||||
const hero = heroesList.get(0);
|
||||
|
||||
await hero.click();
|
||||
await sleepFor(250);
|
||||
const newTotal = await heroesList.count();
|
||||
|
||||
expect(newTotal).toBeLessThan(total);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Filter/Stagger Component', () => {
|
||||
beforeAll(async () => {
|
||||
await filterHref.click();
|
||||
sleepFor();
|
||||
});
|
||||
|
||||
it('should attach a filterAnimations trigger to the list container', async () => {
|
||||
const heroesList = filterStagger.getComponentContainer();
|
||||
const cssClasses = await heroesList.getAttribute('class');
|
||||
|
||||
expect(cssClasses).toContain('ng-trigger-filterAnimation');
|
||||
});
|
||||
|
||||
it('should filter down the list when a search is performed', async () => {
|
||||
const heroesList = filterStagger.getHeroesList();
|
||||
const total = await heroesList.count();
|
||||
const formInput = filterStagger.getFormInput();
|
||||
|
||||
await formInput.sendKeys('Mag');
|
||||
await sleepFor(500);
|
||||
const newTotal = await heroesList.count();
|
||||
|
||||
expect(newTotal).toBeLessThan(total);
|
||||
expect(newTotal).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Hero Groups Component', () => {
|
||||
beforeAll(async () => {
|
||||
await heroGroupsHref.click();
|
||||
sleepFor(300);
|
||||
});
|
||||
|
||||
it('should attach a flyInOut trigger to the list of items', async () => {
|
||||
const heroesList = heroGroups.getHeroesList();
|
||||
const hero = heroesList.get(0);
|
||||
const cssClasses = await hero.getAttribute('class');
|
||||
const transform = await hero.getCssValue('transform');
|
||||
const opacity = await hero.getCssValue('opacity');
|
||||
|
||||
expect(cssClasses).toContain('ng-trigger-flyInOut');
|
||||
expect(transform).toBe('matrix(1, 0, 0, 1, 0, 0)');
|
||||
expect(opacity).toBe('1');
|
||||
});
|
||||
|
||||
it('should remove the hero from the list when clicked', async () => {
|
||||
const heroesList = heroGroups.getHeroesList();
|
||||
const total = await heroesList.count();
|
||||
const hero = heroesList.get(0);
|
||||
|
||||
await hero.click();
|
||||
await sleepFor(300);
|
||||
const newTotal = await heroesList.count();
|
||||
|
||||
expect(newTotal).toBeLessThan(total);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import { by } from 'protractor';
|
||||
import { locate } from './util';
|
||||
|
||||
export function getPage() {
|
||||
return by.css('app-hero-list-auto-page');
|
||||
}
|
||||
|
||||
export function getComponent() {
|
||||
return by.css('app-hero-list-auto');
|
||||
}
|
||||
|
||||
export function getComponentContainer() {
|
||||
const findContainer = () => by.css('ul');
|
||||
return locate(getComponent(), findContainer());
|
||||
}
|
||||
|
||||
export function getHeroesList() {
|
||||
return getComponentContainer().all(by.css('li'));
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import { by } from 'protractor';
|
||||
import { locate } from './util';
|
||||
|
||||
export function getPage() {
|
||||
return by.css('app-hero-list-enter-leave-page');
|
||||
}
|
||||
|
||||
export function getComponent() {
|
||||
return by.css('app-hero-list-enter-leave');
|
||||
}
|
||||
|
||||
export function getComponentContainer() {
|
||||
const findContainer = () => by.css('ul');
|
||||
return locate(getComponent(), findContainer());
|
||||
}
|
||||
|
||||
export function getHeroesList() {
|
||||
return getComponentContainer().all(by.css('li'));
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import { by } from 'protractor';
|
||||
import { locate } from './util';
|
||||
|
||||
export function getPage() {
|
||||
return by.css('app-hero-list-page');
|
||||
}
|
||||
|
||||
export function getComponentContainer() {
|
||||
const findContainer = () => by.css('ul');
|
||||
return locate(getPage(), findContainer());
|
||||
}
|
||||
|
||||
export function getHeroesList() {
|
||||
return getComponentContainer().all(by.css('li'));
|
||||
}
|
||||
|
||||
export function getFormInput() {
|
||||
const formInput = () => by.css('form > input');
|
||||
return locate(getPage(), formInput());
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import { by } from 'protractor';
|
||||
import { locate } from './util';
|
||||
|
||||
export function getPage() {
|
||||
return by.css('app-hero-list-groups-page');
|
||||
}
|
||||
|
||||
export function getComponent() {
|
||||
return by.css('app-hero-list-groups');
|
||||
}
|
||||
|
||||
export function getComponentContainer() {
|
||||
const findContainer = () => by.css('ul');
|
||||
return locate(getComponent(), findContainer());
|
||||
}
|
||||
|
||||
export function getHeroesList() {
|
||||
return getComponentContainer().all(by.css('li'));
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import { by } from 'protractor';
|
||||
import { locate } from './util';
|
||||
|
||||
export function getPage() {
|
||||
return by.css('app-open-close-page');
|
||||
}
|
||||
|
||||
export function getComponent() {
|
||||
return by.css('app-open-close');
|
||||
}
|
||||
|
||||
export function getToggleButton() {
|
||||
const toggleButton = () => by.buttonText('Toggle Open/Close');
|
||||
return locate(getComponent(), toggleButton());
|
||||
}
|
||||
|
||||
export function getLoggingCheckbox() {
|
||||
const loggingCheckbox = () => by.css('section > input[type="checkbox"]');
|
||||
return locate(getPage(), loggingCheckbox());
|
||||
}
|
||||
|
||||
export function getComponentContainer() {
|
||||
const findContainer = () => by.css('div');
|
||||
return locate(getComponent(), findContainer());
|
||||
}
|
||||
|
||||
export async function getComponentText() {
|
||||
const findContainerText = () => by.css('div');
|
||||
const contents = locate(getComponent(), findContainerText());
|
||||
const componentText = await contents.getText();
|
||||
|
||||
return componentText;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import { by } from 'protractor';
|
||||
import { locate } from './util';
|
||||
|
||||
export function getPage() {
|
||||
return by.css('app-status-slider-page');
|
||||
}
|
||||
|
||||
export function getComponent() {
|
||||
return by.css('app-status-slider');
|
||||
}
|
||||
|
||||
export function getToggleButton() {
|
||||
const toggleButton = () => by.buttonText('Toggle Status');
|
||||
return locate(getComponent(), toggleButton());
|
||||
}
|
||||
|
||||
export function getComponentContainer() {
|
||||
const findContainer = () => by.css('div');
|
||||
return locate(getComponent(), findContainer());
|
||||
}
|
||||
|
||||
export async function getComponentText() {
|
||||
const findContainerText = () => by.css('div');
|
||||
const contents = locate(getComponent(), findContainerText());
|
||||
const componentText = await contents.getText();
|
||||
|
||||
return componentText;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { by } from 'protractor';
|
||||
import { locate } from './util';
|
||||
|
||||
export function getPage() {
|
||||
return by.css('app-toggle-animations-child-page');
|
||||
}
|
||||
|
||||
export function getComponent() {
|
||||
return by.css('app-open-close-toggle');
|
||||
}
|
||||
|
||||
export function getToggleButton() {
|
||||
const toggleButton = () => by.buttonText('Toggle Open/Closed');
|
||||
return locate(getComponent(), toggleButton());
|
||||
}
|
||||
|
||||
export function getToggleAnimationsButton() {
|
||||
const toggleAnimationsButton = () => by.buttonText('Toggle Animations');
|
||||
return locate(getComponent(), toggleAnimationsButton());
|
||||
}
|
||||
|
||||
export function getComponentContainer() {
|
||||
const findContainer = () => by.css('div');
|
||||
return locate(getComponent()).all(findContainer()).get(0);
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import { Locator, ElementFinder, browser, by, element } from 'protractor';
|
||||
|
||||
/**
|
||||
*
|
||||
* locate(finder1, finder2) => element(finder1).element(finder2).element(finderN);
|
||||
*/
|
||||
export function locate(locator: Locator, ...locators: Locator[]) {
|
||||
return locators.reduce((current: ElementFinder, next: Locator) => {
|
||||
return current.element(next);
|
||||
}, element(locator)) as ElementFinder;
|
||||
}
|
||||
|
||||
export async function sleepFor(time = 1000) {
|
||||
return await browser.sleep(time);
|
||||
}
|
||||
|
||||
export function getLinkById(id: string) {
|
||||
return element(by.css(`a[id=${id}]`));
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
<p>
|
||||
Angular's animations library makes it easy to define and apply animation effects such as page and list transitions.
|
||||
</p>
|
|
@ -0,0 +1,15 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-about',
|
||||
templateUrl: './about.component.html',
|
||||
styleUrls: ['./about.component.css']
|
||||
})
|
||||
export class AboutComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// #docregion
|
||||
import { animation, style, animate } from '@angular/animations';
|
||||
|
||||
export const transAnimation = animation([
|
||||
style({
|
||||
height: '{{ height }}',
|
||||
opacity: '{{ opacity }}',
|
||||
backgroundColor: '{{ backgroundColor }}'
|
||||
}),
|
||||
animate('{{ time }}')
|
||||
]);
|
|
@ -0,0 +1,74 @@
|
|||
// #docregion reusable
|
||||
import {
|
||||
animation, trigger, animateChild, group,
|
||||
transition, animate, style, query
|
||||
} from '@angular/animations';
|
||||
|
||||
export const transAnimation = animation([
|
||||
style({
|
||||
height: '{{ height }}',
|
||||
opacity: '{{ opacity }}',
|
||||
backgroundColor: '{{ backgroundColor }}'
|
||||
}),
|
||||
animate('{{ time }}')
|
||||
]);
|
||||
// #enddocregion reusable
|
||||
|
||||
// Routable animations
|
||||
// #docregion route-animations
|
||||
export const slideInAnimation =
|
||||
// #docregion style-view
|
||||
trigger('routeAnimations', [
|
||||
transition('HomePage <=> AboutPage', [
|
||||
style({ position: 'relative' }),
|
||||
query(':enter, :leave', [
|
||||
style({
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%'
|
||||
})
|
||||
]),
|
||||
// #enddocregion style-view
|
||||
// #docregion query
|
||||
query(':enter', [
|
||||
style({ left: '-100%'})
|
||||
]),
|
||||
query(':leave', animateChild()),
|
||||
group([
|
||||
query(':leave', [
|
||||
animate('300ms ease-out', style({ left: '100%'}))
|
||||
]),
|
||||
query(':enter', [
|
||||
animate('300ms ease-out', style({ left: '0%'}))
|
||||
])
|
||||
]),
|
||||
query(':enter', animateChild()),
|
||||
]),
|
||||
transition('* <=> FilterPage', [
|
||||
style({ position: 'relative' }),
|
||||
query(':enter, :leave', [
|
||||
style({
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%'
|
||||
})
|
||||
]),
|
||||
query(':enter', [
|
||||
style({ left: '-100%'})
|
||||
]),
|
||||
query(':leave', animateChild()),
|
||||
group([
|
||||
query(':leave', [
|
||||
animate('200ms ease-out', style({ left: '100%'}))
|
||||
]),
|
||||
query(':enter', [
|
||||
animate('300ms ease-out', style({ left: '0%'}))
|
||||
])
|
||||
]),
|
||||
query(':enter', animateChild()),
|
||||
])
|
||||
// #enddocregion query
|
||||
]);
|
||||
// #enddocregion route-animations
|
|
@ -0,0 +1,35 @@
|
|||
// #docplaster
|
||||
// #docregion imports
|
||||
import { Component, HostBinding } from '@angular/core';
|
||||
import {
|
||||
trigger,
|
||||
state,
|
||||
style,
|
||||
animate,
|
||||
transition,
|
||||
// ...
|
||||
} from '@angular/animations';
|
||||
|
||||
// #enddocregion imports
|
||||
|
||||
// #docregion decorator, toggle-app-animations
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: 'app.component.html',
|
||||
styleUrls: ['app.component.css'],
|
||||
animations: [
|
||||
// animation triggers go here
|
||||
]
|
||||
})
|
||||
// #enddocregion decorator
|
||||
export class AppComponent {
|
||||
@HostBinding('@.disabled')
|
||||
public animationsDisabled = false;
|
||||
// #enddocregion toggle-app-animations
|
||||
|
||||
toggleAnimations() {
|
||||
this.animationsDisabled = !this.animationsDisabled;
|
||||
}
|
||||
// #docregion toggle-app-animations
|
||||
}
|
||||
// #enddocregion toggle-app-animations
|
|
@ -0,0 +1,7 @@
|
|||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
section {
|
||||
margin-top: 100px;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<h1>Animations</h1>
|
||||
|
||||
Toggle All Animations <input type="checkbox" [checked]="!animationsDisabled" (click)="toggleAnimations()"/>
|
||||
|
||||
<nav>
|
||||
<a id="home" routerLink="/home" routerLinkActive="active">Home</a>
|
||||
<a id="about" routerLink="/about" routerLinkActive="active">About</a>
|
||||
<a id="open-close" routerLink="/open-close" routerLinkActive="active">Open/Close</a>
|
||||
<a id="status" routerLink="/status" routerLinkActive="active">Status Slider</a>
|
||||
<a id="toggle" routerLink="/toggle" routerLinkActive="active">Toggle Animations</a>
|
||||
<a id="enter-leave" routerLink="/enter-leave" routerLinkActive="active">Enter/Leave</a>
|
||||
<a id="auto" routerLink="/auto" routerLinkActive="active">Auto Calculation</a>
|
||||
<a id="heroes" routerLink="/heroes" routerLinkActive="active">Filter/Stagger</a>
|
||||
<a id="hero-groups" routerLink="/hero-groups" routerLinkActive="active">Hero Groups</a>
|
||||
</nav>
|
||||
|
||||
<!-- #docregion route-animations-outlet -->
|
||||
<div [@routeAnimations]="prepareRoute(outlet)" >
|
||||
<router-outlet #outlet="outlet"></router-outlet>
|
||||
</div>
|
||||
<!-- #enddocregion route-animations-outlet -->
|
|
@ -0,0 +1,47 @@
|
|||
// #docplaster
|
||||
// #docregion imports
|
||||
import { Component, HostBinding } from '@angular/core';
|
||||
import {
|
||||
trigger,
|
||||
state,
|
||||
style,
|
||||
animate,
|
||||
transition,
|
||||
// ...
|
||||
} from '@angular/animations';
|
||||
|
||||
// #enddocregion imports
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
import { slideInAnimation } from './animations';
|
||||
|
||||
// #docregion decorator, toggle-app-animations, define
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: 'app.component.html',
|
||||
styleUrls: ['app.component.css'],
|
||||
animations: [
|
||||
// #enddocregion decorator
|
||||
slideInAnimation
|
||||
// #docregion decorator
|
||||
// animation triggers go here
|
||||
]
|
||||
})
|
||||
// #enddocregion decorator, define
|
||||
export class AppComponent {
|
||||
@HostBinding('@.disabled')
|
||||
public animationsDisabled = false;
|
||||
// #enddocregion toggle-app-animations
|
||||
|
||||
// #docregion prepare-router-outlet
|
||||
prepareRoute(outlet: RouterOutlet) {
|
||||
return outlet && outlet.activatedRouteData && outlet.activatedRouteData['animation'];
|
||||
}
|
||||
|
||||
// #enddocregion prepare-router-outlet
|
||||
|
||||
toggleAnimations() {
|
||||
this.animationsDisabled = !this.animationsDisabled;
|
||||
}
|
||||
// #docregion toggle-app-animations
|
||||
}
|
||||
// #enddocregion toggle-app-animations
|
|
@ -0,0 +1,13 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule
|
||||
],
|
||||
declarations: [ ],
|
||||
bootstrap: [ ]
|
||||
})
|
||||
export class AppModule { }
|
|
@ -1,43 +1,63 @@
|
|||
// #docplaster
|
||||
// #docregion route-animation-data
|
||||
import { NgModule } from '@angular/core';
|
||||
// #docregion animations-module
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
// #enddocregion animations-module
|
||||
|
||||
import { HeroTeamBuilderComponent } from './hero-team-builder.component';
|
||||
import { HeroListBasicComponent } from './hero-list-basic.component';
|
||||
import { HeroListInlineStylesComponent } from './hero-list-inline-styles.component';
|
||||
import { HeroListEnterLeaveComponent } from './hero-list-enter-leave.component';
|
||||
import { HeroListEnterLeaveStatesComponent } from './hero-list-enter-leave-states.component';
|
||||
import { HeroListCombinedTransitionsComponent } from './hero-list-combined-transitions.component';
|
||||
import { HeroListTwowayComponent } from './hero-list-twoway.component';
|
||||
import { HeroListAutoComponent } from './hero-list-auto.component';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { AppComponent } from './app.component';
|
||||
import { OpenCloseComponent } from './open-close.component';
|
||||
import { OpenClosePageComponent } from './open-close-page.component';
|
||||
import { OpenCloseChildComponent } from './open-close.component.4';
|
||||
import { ToggleAnimationsPageComponent } from './toggle-animations-page.component';
|
||||
import { StatusSliderComponent } from './status-slider.component';
|
||||
import { StatusSliderPageComponent } from './status-slider-page.component';
|
||||
import { HeroListPageComponent } from './hero-list-page.component';
|
||||
import { HeroListGroupPageComponent } from './hero-list-group-page.component';
|
||||
import { HeroListGroupsComponent } from './hero-list-groups.component';
|
||||
import { HeroListMultistepComponent } from './hero-list-multistep.component';
|
||||
import { HeroListTimingsComponent } from './hero-list-timings.component';
|
||||
// #docregion animations-module
|
||||
import { HeroListEnterLeavePageComponent } from './hero-list-enter-leave-page.component';
|
||||
import { HeroListEnterLeaveComponent } from './hero-list-enter-leave.component';
|
||||
import { HeroListAutoCalcPageComponent } from './hero-list-auto-page.component';
|
||||
import { HeroListAutoComponent } from './hero-list-auto.component';
|
||||
import { HomeComponent } from './home.component';
|
||||
import { AboutComponent } from './about.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [ BrowserModule, BrowserAnimationsModule ],
|
||||
// ... more stuff ...
|
||||
// #enddocregion animations-module
|
||||
declarations: [
|
||||
HeroTeamBuilderComponent,
|
||||
HeroListBasicComponent,
|
||||
HeroListInlineStylesComponent,
|
||||
HeroListCombinedTransitionsComponent,
|
||||
HeroListTwowayComponent,
|
||||
HeroListEnterLeaveComponent,
|
||||
HeroListEnterLeaveStatesComponent,
|
||||
HeroListAutoComponent,
|
||||
HeroListTimingsComponent,
|
||||
HeroListMultistepComponent,
|
||||
HeroListGroupsComponent
|
||||
imports: [
|
||||
BrowserModule,
|
||||
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: 'home', component: HomeComponent, data: {animation: 'HomePage'} },
|
||||
{ path: 'about', component: AboutComponent, data: {animation: 'AboutPage'} },
|
||||
|
||||
])
|
||||
],
|
||||
bootstrap: [ HeroTeamBuilderComponent ]
|
||||
// #docregion animations-module
|
||||
// #enddocregion route-animation-data
|
||||
declarations: [
|
||||
AppComponent,
|
||||
StatusSliderComponent,
|
||||
OpenCloseComponent,
|
||||
OpenCloseChildComponent,
|
||||
OpenClosePageComponent,
|
||||
StatusSliderPageComponent,
|
||||
ToggleAnimationsPageComponent,
|
||||
HeroListPageComponent,
|
||||
HeroListGroupsComponent,
|
||||
HeroListGroupPageComponent,
|
||||
HeroListEnterLeavePageComponent,
|
||||
HeroListEnterLeaveComponent,
|
||||
HeroListAutoCalcPageComponent,
|
||||
HeroListAutoComponent,
|
||||
HomeComponent,
|
||||
AboutComponent
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
||||
// #enddocregion animations-module
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { HEROES } from './mock-heroes';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list-auto-page',
|
||||
template: `
|
||||
<section>
|
||||
<h2>Automatic Calculation</h2>
|
||||
|
||||
<app-hero-list-auto [heroes]="heroes" (remove)="onRemove($event)"></app-hero-list-auto>
|
||||
</section>
|
||||
`
|
||||
})
|
||||
export class HeroListAutoCalcPageComponent {
|
||||
heroes = HEROES.slice();
|
||||
|
||||
onRemove(id: number) {
|
||||
this.heroes = this.heroes.filter(hero => hero.id !== id);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<ul class="heroes">
|
||||
<li *ngFor="let hero of heroes"
|
||||
[@shrinkOut]="'in'" (click)="removeHero(hero.id)">
|
||||
<div class="inner">
|
||||
<span class="badge">{{ hero.id }}</span>
|
||||
<span>{{ hero.name }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
|
@ -1,6 +1,8 @@
|
|||
import {
|
||||
Component,
|
||||
Input
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter
|
||||
} from '@angular/core';
|
||||
import {
|
||||
trigger,
|
||||
|
@ -10,38 +12,30 @@ import {
|
|||
transition
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Hero } from './hero.service';
|
||||
import { Hero } from './hero';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list-auto',
|
||||
// #docregion template
|
||||
template: `
|
||||
<ul>
|
||||
<li *ngFor="let hero of heroes"
|
||||
[@shrinkOut]="'in'">
|
||||
{{hero.name}}
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
// #enddocregion template
|
||||
styleUrls: ['./hero-list.component.css'],
|
||||
|
||||
/* When the element leaves (transition "in => void" occurs),
|
||||
* get the element's current computed height and animate
|
||||
* it down to 0.
|
||||
*/
|
||||
// #docregion animationdef
|
||||
templateUrl: 'hero-list-auto.component.html',
|
||||
styleUrls: ['./hero-list-page.component.css'],
|
||||
// #docregion auto-calc
|
||||
animations: [
|
||||
trigger('shrinkOut', [
|
||||
state('in', style({height: '*'})),
|
||||
state('in', style({ height: '*' })),
|
||||
transition('* => void', [
|
||||
style({height: '*'}),
|
||||
animate(250, style({height: 0}))
|
||||
style({ height: '*' }),
|
||||
animate(250, style({ height: 0 }))
|
||||
])
|
||||
])
|
||||
]
|
||||
// #enddocregion animationdef
|
||||
// #enddocregion auto-calc
|
||||
})
|
||||
export class HeroListAutoComponent {
|
||||
@Input() heroes: Hero[];
|
||||
|
||||
@Output() remove = new EventEmitter<number>();
|
||||
|
||||
removeHero(id: number) {
|
||||
this.remove.emit(id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
// #docregion imports
|
||||
import {
|
||||
Component,
|
||||
Input
|
||||
} from '@angular/core';
|
||||
import {
|
||||
trigger,
|
||||
state,
|
||||
style,
|
||||
animate,
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
// #enddocregion imports
|
||||
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list-basic',
|
||||
// #enddocregion
|
||||
/* The click event calls hero.toggleState(), which
|
||||
* causes the state of that hero to switch from
|
||||
* active to inactive or vice versa.
|
||||
*/
|
||||
// #docregion
|
||||
// #docregion template
|
||||
template: `
|
||||
<ul>
|
||||
<li *ngFor="let hero of heroes"
|
||||
[@heroState]="hero.state"
|
||||
(click)="hero.toggleState()">
|
||||
{{hero.name}}
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
// #enddocregion template
|
||||
styleUrls: ['./hero-list.component.css'],
|
||||
// #enddocregion
|
||||
/**
|
||||
* Define two states, "inactive" and "active", and the end
|
||||
* styles that apply whenever the element is in those states.
|
||||
* Then define animations for transitioning between the states,
|
||||
* one in each direction
|
||||
*/
|
||||
// #docregion
|
||||
// #docregion animationdef
|
||||
animations: [
|
||||
trigger('heroState', [
|
||||
// #docregion states
|
||||
state('inactive', style({
|
||||
backgroundColor: '#eee',
|
||||
transform: 'scale(1)'
|
||||
})),
|
||||
state('active', style({
|
||||
backgroundColor: '#cfd8dc',
|
||||
transform: 'scale(1.1)'
|
||||
})),
|
||||
// #enddocregion states
|
||||
// #docregion transitions
|
||||
transition('inactive => active', animate('100ms ease-in')),
|
||||
transition('active => inactive', animate('100ms ease-out'))
|
||||
// #enddocregion transitions
|
||||
])
|
||||
]
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListBasicComponent {
|
||||
@Input() heroes: Hero[];
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
// #docregion
|
||||
// #docregion imports
|
||||
import {
|
||||
Component,
|
||||
Input
|
||||
} from '@angular/core';
|
||||
import {
|
||||
trigger,
|
||||
state,
|
||||
style,
|
||||
animate,
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
// #enddocregion imports
|
||||
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list-combined-transitions',
|
||||
// #docregion template
|
||||
template: `
|
||||
<ul>
|
||||
<li *ngFor="let hero of heroes"
|
||||
[@heroState]="hero.state"
|
||||
(click)="hero.toggleState()">
|
||||
{{hero.name}}
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
// #enddocregion template
|
||||
styleUrls: ['./hero-list.component.css'],
|
||||
/*
|
||||
* Define two states, "inactive" and "active", and the end
|
||||
* styles that apply whenever the element is in those states.
|
||||
* Then define an animated transition between these two
|
||||
* states, in *both* directions.
|
||||
*/
|
||||
// #docregion animationdef
|
||||
animations: [
|
||||
trigger('heroState', [
|
||||
state('inactive', style({
|
||||
backgroundColor: '#eee',
|
||||
transform: 'scale(1)'
|
||||
})),
|
||||
state('active', style({
|
||||
backgroundColor: '#cfd8dc',
|
||||
transform: 'scale(1.1)'
|
||||
})),
|
||||
// #docregion transitions
|
||||
transition('inactive => active, active => inactive',
|
||||
animate('100ms ease-out'))
|
||||
// #enddocregion transitions
|
||||
])
|
||||
]
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListCombinedTransitionsComponent {
|
||||
@Input() heroes: Hero[];
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { HEROES } from './mock-heroes';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list-enter-leave-page',
|
||||
template: `
|
||||
<section>
|
||||
<h2>Enter/Leave</h2>
|
||||
|
||||
<app-hero-list-enter-leave [heroes]="heroes" (remove)="onRemove($event)"></app-hero-list-enter-leave>
|
||||
</section>
|
||||
`
|
||||
})
|
||||
export class HeroListEnterLeavePageComponent {
|
||||
heroes = HEROES.slice();
|
||||
|
||||
onRemove(id: number) {
|
||||
this.heroes = this.heroes.filter(hero => hero.id !== id);
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
import {
|
||||
Component,
|
||||
Input
|
||||
} from '@angular/core';
|
||||
import {
|
||||
trigger,
|
||||
state,
|
||||
style,
|
||||
animate,
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list-enter-leave-states',
|
||||
// #docregion template
|
||||
template: `
|
||||
<ul>
|
||||
<li *ngFor="let hero of heroes"
|
||||
(click)="hero.toggleState()"
|
||||
[@heroState]="hero.state">
|
||||
{{hero.name}}
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
// #enddocregion template
|
||||
styleUrls: ['./hero-list.component.css'],
|
||||
/* The elements here have two possible states based
|
||||
* on the hero state, "active", or "inactive". We animate
|
||||
* six transitions: Between the two states in both directions,
|
||||
* and between each state and void. With this we can animate
|
||||
* the enter and leave of elements differently based on which
|
||||
* state they are in when they are added and removed.
|
||||
*/
|
||||
// #docregion animationdef
|
||||
animations: [
|
||||
trigger('heroState', [
|
||||
state('inactive', style({transform: 'translateX(0) scale(1)'})),
|
||||
state('active', style({transform: 'translateX(0) scale(1.1)'})),
|
||||
transition('inactive => active', animate('100ms ease-in')),
|
||||
transition('active => inactive', animate('100ms ease-out')),
|
||||
transition('void => inactive', [
|
||||
style({transform: 'translateX(-100%) scale(1)'}),
|
||||
animate(100)
|
||||
]),
|
||||
transition('inactive => void', [
|
||||
animate(100, style({transform: 'translateX(100%) scale(1)'}))
|
||||
]),
|
||||
transition('void => active', [
|
||||
style({transform: 'translateX(0) scale(0)'}),
|
||||
animate(200)
|
||||
]),
|
||||
transition('active => void', [
|
||||
animate(200, style({transform: 'translateX(0) scale(0)'}))
|
||||
])
|
||||
])
|
||||
]
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListEnterLeaveStatesComponent {
|
||||
@Input() heroes: Hero[];
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
import {
|
||||
Component,
|
||||
Input
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter
|
||||
} from '@angular/core';
|
||||
import {
|
||||
trigger,
|
||||
|
@ -10,42 +12,45 @@ import {
|
|||
transition
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Hero } from './hero.service';
|
||||
import { Hero } from './hero';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list-enter-leave',
|
||||
// #docregion template
|
||||
template: `
|
||||
<ul>
|
||||
<ul class="heroes">
|
||||
<li *ngFor="let hero of heroes"
|
||||
[@flyInOut]="'in'">
|
||||
{{hero.name}}
|
||||
[@flyInOut]="'in'" (click)="removeHero(hero.id)">
|
||||
<div class="inner">
|
||||
<span class="badge">{{ hero.id }}</span>
|
||||
<span>{{ hero.name }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
// #enddocregion template
|
||||
styleUrls: ['./hero-list.component.css'],
|
||||
/* The element here always has the state "in" when it
|
||||
* is present. We animate two transitions: From void
|
||||
* to in and from in to void, to achieve an animated
|
||||
* enter and leave transition. The element enters from
|
||||
* the left and leaves to the right using translateX.
|
||||
*/
|
||||
styleUrls: ['./hero-list-page.component.css'],
|
||||
// #docregion animationdef
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({transform: 'translateX(0)'})),
|
||||
state('in', style({ transform: 'translateX(0)' })),
|
||||
transition('void => *', [
|
||||
style({transform: 'translateX(-100%)'}),
|
||||
style({ transform: 'translateX(-100%)' }),
|
||||
animate(100)
|
||||
]),
|
||||
transition('* => void', [
|
||||
animate(100, style({transform: 'translateX(100%)'}))
|
||||
animate(100, style({ transform: 'translateX(100%)' }))
|
||||
])
|
||||
])
|
||||
]
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListEnterLeaveComponent {
|
||||
@Input() heroes: Hero[];
|
||||
@Input() heroes: Hero[];
|
||||
|
||||
@Output() remove = new EventEmitter<number>();
|
||||
|
||||
removeHero(id: number) {
|
||||
this.remove.emit(id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { HEROES } from './mock-heroes';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list-groups-page',
|
||||
template: `
|
||||
<section>
|
||||
<h2>Hero List Group</h2>
|
||||
|
||||
<app-hero-list-groups [heroes]="heroes" (remove)="onRemove($event)"></app-hero-list-groups>
|
||||
</section>
|
||||
`
|
||||
})
|
||||
export class HeroListGroupPageComponent {
|
||||
heroes = HEROES.slice();
|
||||
|
||||
onRemove(id: number) {
|
||||
this.heroes = this.heroes.filter(hero => hero.id !== id);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
import {
|
||||
Component,
|
||||
Input
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter
|
||||
} from '@angular/core';
|
||||
import {
|
||||
trigger,
|
||||
|
@ -11,45 +13,31 @@ import {
|
|||
group
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Hero } from './hero.service';
|
||||
import { Hero } from './hero';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list-groups',
|
||||
template: `
|
||||
<ul>
|
||||
<ul class="heroes">
|
||||
<li *ngFor="let hero of heroes"
|
||||
[@flyInOut]="'in'">
|
||||
{{hero.name}}
|
||||
[@flyInOut]="'in'" (click)="removeHero(hero.id)">
|
||||
<div class="inner">
|
||||
<span class="badge">{{ hero.id }}</span>
|
||||
<span>{{ hero.name }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
styleUrls: ['./hero-list.component.css'],
|
||||
styles: [`
|
||||
li {
|
||||
padding: 0 !important;
|
||||
text-align: center;
|
||||
}
|
||||
`],
|
||||
/* The element here always has the state "in" when it
|
||||
* is present. We animate two transitions: From void
|
||||
* to in and from in to void, to achieve an animated
|
||||
* enter and leave transition.
|
||||
*
|
||||
* The transitions have *parallel group* that allow
|
||||
* animating several properties at the same time but
|
||||
* with different timing configurations. On enter
|
||||
* (void => *) we start the opacity animation 0.1s
|
||||
* earlier than the translation/width animation.
|
||||
* On leave (* => void) we do the opposite -
|
||||
* the translation/width animation begins immediately
|
||||
* and the opacity animation 0.1s later.
|
||||
*/
|
||||
styleUrls: ['./hero-list-page.component.css'],
|
||||
// #docregion animationdef
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({width: 120, transform: 'translateX(0)', opacity: 1})),
|
||||
state('in', style({
|
||||
width: 120,
|
||||
transform: 'translateX(0)', opacity: 1
|
||||
})),
|
||||
transition('void => *', [
|
||||
style({width: 10, transform: 'translateX(50px)', opacity: 0}),
|
||||
style({ width: 10, transform: 'translateX(50px)', opacity: 0 }),
|
||||
group([
|
||||
animate('0.3s 0.1s ease', style({
|
||||
transform: 'translateX(0)',
|
||||
|
@ -77,4 +65,10 @@ import { Hero } from './hero.service';
|
|||
})
|
||||
export class HeroListGroupsComponent {
|
||||
@Input() heroes: Hero[];
|
||||
|
||||
@Output() remove = new EventEmitter<number>();
|
||||
|
||||
removeHero(id: number) {
|
||||
this.remove.emit(id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
// #docregion
|
||||
// #docregion imports
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
trigger,
|
||||
style,
|
||||
animate,
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
// #enddocregion imports
|
||||
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list-inline-styles',
|
||||
// #docregion template
|
||||
template: `
|
||||
<ul>
|
||||
<li *ngFor="let hero of heroes"
|
||||
[@heroState]="hero.state"
|
||||
(click)="hero.toggleState()">
|
||||
{{hero.name}}
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
// #enddocregion template
|
||||
styleUrls: ['./hero-list.component.css'],
|
||||
/**
|
||||
* Define two states, "inactive" and "active", and the end
|
||||
* styles that apply whenever the element is in those states.
|
||||
* Then define an animation for the inactive => active transition.
|
||||
* This animation has no end styles, but only styles that are
|
||||
* defined inline inside the transition and thus are only kept
|
||||
* as long as the animation is running.
|
||||
*/
|
||||
// #docregion animationdef
|
||||
animations: [
|
||||
trigger('heroState', [
|
||||
// #docregion transitions
|
||||
transition('inactive => active', [
|
||||
style({
|
||||
backgroundColor: '#cfd8dc',
|
||||
transform: 'scale(1.3)'
|
||||
}),
|
||||
animate('80ms ease-in', style({
|
||||
backgroundColor: '#eee',
|
||||
transform: 'scale(1)'
|
||||
}))
|
||||
]),
|
||||
// #enddocregion transitions
|
||||
])
|
||||
]
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListInlineStylesComponent {
|
||||
@Input() heroes: Hero[];
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
import {
|
||||
Component,
|
||||
Input,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
trigger,
|
||||
state,
|
||||
style,
|
||||
animate,
|
||||
transition,
|
||||
keyframes,
|
||||
AnimationEvent
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list-multistep',
|
||||
// #docregion template
|
||||
template: `
|
||||
<ul>
|
||||
<li *ngFor="let hero of heroes"
|
||||
(@flyInOut.start)="animationStarted($event)"
|
||||
(@flyInOut.done)="animationDone($event)"
|
||||
[@flyInOut]="'in'">
|
||||
{{hero.name}}
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
// #enddocregion template
|
||||
styleUrls: ['./hero-list.component.css'],
|
||||
/* The element here always has the state "in" when it
|
||||
* is present. We animate two transitions: From void
|
||||
* to in and from in to void, to achieve an animated
|
||||
* enter and leave transition. Each transition is
|
||||
* defined in terms of multiple keyframes, to give it
|
||||
* a bounce effect.
|
||||
*/
|
||||
// #docregion animationdef
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({transform: 'translateX(0)'})),
|
||||
transition('void => *', [
|
||||
animate(300, keyframes([
|
||||
style({opacity: 0, transform: 'translateX(-100%)', offset: 0}),
|
||||
style({opacity: 1, transform: 'translateX(15px)', offset: 0.3}),
|
||||
style({opacity: 1, transform: 'translateX(0)', offset: 1.0})
|
||||
]))
|
||||
]),
|
||||
transition('* => void', [
|
||||
animate(300, keyframes([
|
||||
style({opacity: 1, transform: 'translateX(0)', offset: 0}),
|
||||
style({opacity: 1, transform: 'translateX(-15px)', offset: 0.7}),
|
||||
style({opacity: 0, transform: 'translateX(100%)', offset: 1.0})
|
||||
]))
|
||||
])
|
||||
])
|
||||
]
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListMultistepComponent {
|
||||
@Input() heroes: Hero[];
|
||||
|
||||
animationStarted(event: AnimationEvent) {
|
||||
console.warn('Animation started: ', event);
|
||||
}
|
||||
|
||||
animationDone(event: AnimationEvent) {
|
||||
console.warn('Animation done: ', event);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
.heroes {
|
||||
margin: 0 0 2em 0;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
width: 15em;
|
||||
}
|
||||
|
||||
.heroes li {
|
||||
position: relative;
|
||||
height: 2.3em;
|
||||
overflow:hidden;
|
||||
margin: .5em;
|
||||
}
|
||||
|
||||
.heroes li > .inner {
|
||||
cursor: pointer;
|
||||
background-color: #EEE;
|
||||
padding: .3em 0;
|
||||
height: 1.6em;
|
||||
border-radius: 4px;
|
||||
width: 19em;
|
||||
}
|
||||
|
||||
.heroes li:hover > .inner {
|
||||
color: #607D8B;
|
||||
background-color: #DDD;
|
||||
transform: translateX(.1em);
|
||||
}
|
||||
|
||||
.heroes a {
|
||||
color: #888;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.heroes a:hover {
|
||||
color:#607D8B;
|
||||
}
|
||||
|
||||
.heroes .badge {
|
||||
display: inline-block;
|
||||
font-size: small;
|
||||
color: white;
|
||||
padding: 0.8em 0.7em 0 0.7em;
|
||||
background-color: #607D8B;
|
||||
line-height: 1em;
|
||||
position: relative;
|
||||
left: -1px;
|
||||
top: -4px;
|
||||
height: 1.8em;
|
||||
min-width: 16px;
|
||||
text-align: right;
|
||||
margin-right: .8em;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.button {
|
||||
background-color: #eee;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
cursor: hand;
|
||||
font-family: Arial;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #cfd8dc;
|
||||
}
|
||||
|
||||
button.delete {
|
||||
position: relative;
|
||||
left: 24em;
|
||||
top: -32px;
|
||||
background-color: gray !important;
|
||||
color: white;
|
||||
display: inherit;
|
||||
padding: 5px 8px;
|
||||
width: 2em;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 100%;
|
||||
margin-bottom: 2px;
|
||||
width: 11em;
|
||||
}
|
||||
|
||||
.heroes input {
|
||||
position: relative;
|
||||
top: -3px;
|
||||
width: 12em;
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<!-- #docplaster -->
|
||||
<h2>Filter/Stagger</h2>
|
||||
|
||||
<form>
|
||||
<input #criteria (input)="updateCriteria(criteria.value)" placeholder="Search Heroes" />
|
||||
</form>
|
||||
|
||||
<!-- #docregion filter-animations -->
|
||||
<ul class="heroes" [@filterAnimation]="heroTotal">
|
||||
<!-- #enddocregion filter-animations -->
|
||||
<li *ngFor="let hero of heroes" class="hero">
|
||||
<div class="inner">
|
||||
<span class="badge">{{ hero.id }}</span>
|
||||
<span>{{ hero.name }}</span>
|
||||
</div>
|
||||
</li>
|
||||
<!-- #docregion filter-animations -->
|
||||
</ul>
|
||||
<!-- #enddocregion filter-animations -->
|
|
@ -0,0 +1,81 @@
|
|||
// #docplaster
|
||||
import { Component, HostBinding, OnInit } from '@angular/core';
|
||||
import { trigger, transition, animate, style, query, stagger } from '@angular/animations';
|
||||
import { HEROES } from './mock-heroes';
|
||||
|
||||
// #docregion filter-animations
|
||||
@Component({
|
||||
// #enddocregion filter-animations
|
||||
selector: 'app-hero-list-page',
|
||||
templateUrl: 'hero-list-page.component.html',
|
||||
styleUrls: ['hero-list-page.component.css'],
|
||||
// #docregion page-animations, filter-animations
|
||||
animations: [
|
||||
// #enddocregion filter-animations
|
||||
trigger('pageAnimations', [
|
||||
transition(':enter', [
|
||||
query('.hero, form', [
|
||||
style({opacity: 0, transform: 'translateY(-100px)'}),
|
||||
stagger(-30, [
|
||||
animate('500ms cubic-bezier(0.35, 0, 0.25, 1)', style({ opacity: 1, transform: 'none' }))
|
||||
])
|
||||
])
|
||||
])
|
||||
]),
|
||||
// #enddocregion page-animations
|
||||
// #docregion increment
|
||||
// #docregion filter-animations
|
||||
trigger('filterAnimation', [
|
||||
transition(':enter, * => 0, * => -1', []),
|
||||
transition(':increment', [
|
||||
query(':enter', [
|
||||
style({ opacity: 0, width: '0px' }),
|
||||
stagger(50, [
|
||||
animate('300ms ease-out', style({ opacity: 1, width: '*' })),
|
||||
]),
|
||||
], { optional: true })
|
||||
]),
|
||||
transition(':decrement', [
|
||||
query(':leave', [
|
||||
stagger(50, [
|
||||
animate('300ms ease-out', style({ opacity: 0, width: '0px' })),
|
||||
]),
|
||||
])
|
||||
]),
|
||||
]),
|
||||
// #enddocregion increment
|
||||
// #docregion page-animations
|
||||
]
|
||||
})
|
||||
export class HeroListPageComponent implements OnInit {
|
||||
// #enddocregion filter-animations
|
||||
@HostBinding('@pageAnimations')
|
||||
public animatePage = true;
|
||||
|
||||
_heroes = [];
|
||||
// #docregion filter-animations
|
||||
heroTotal = -1;
|
||||
// #enddocregion filter-animations
|
||||
get heroes() {
|
||||
return this._heroes;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._heroes = HEROES;
|
||||
}
|
||||
|
||||
updateCriteria(criteria: string) {
|
||||
criteria = criteria ? criteria.trim() : '';
|
||||
|
||||
this._heroes = HEROES.filter(hero => hero.name.toLowerCase().includes(criteria.toLowerCase()));
|
||||
const newTotal = this.heroes.length;
|
||||
|
||||
if (this.heroTotal !== newTotal) {
|
||||
this.heroTotal = newTotal;
|
||||
} else if (!criteria) {
|
||||
this.heroTotal = -1;
|
||||
}
|
||||
}
|
||||
// #docregion filter-animations
|
||||
}
|
||||
// #enddocregion filter-animations
|
|
@ -1,58 +0,0 @@
|
|||
import {
|
||||
Component,
|
||||
Input
|
||||
} from '@angular/core';
|
||||
import {
|
||||
trigger,
|
||||
state,
|
||||
style,
|
||||
animate,
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list-timings',
|
||||
template: `
|
||||
<ul>
|
||||
<li *ngFor="let hero of heroes"
|
||||
[@flyInOut]="'in'"
|
||||
(click)="hero.toggleState()">
|
||||
{{hero.name}}
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
styleUrls: ['./hero-list.component.css'],
|
||||
/* The element here always has the state "in" when it
|
||||
* is present. We animate two transitions: From void
|
||||
* to in and from in to void, to achieve an animated
|
||||
* enter and leave transition. The element enters from
|
||||
* the left and leaves to the right using translateX,
|
||||
* and fades in/out using opacity. We use different easings
|
||||
* for enter and leave.
|
||||
*/
|
||||
// #docregion animationdef
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({opacity: 1, transform: 'translateX(0)'})),
|
||||
transition('void => *', [
|
||||
style({
|
||||
opacity: 0,
|
||||
transform: 'translateX(-100%)'
|
||||
}),
|
||||
animate('0.2s ease-in')
|
||||
]),
|
||||
transition('* => void', [
|
||||
animate('0.2s 0.1s ease-out', style({
|
||||
opacity: 0,
|
||||
transform: 'translateX(100%)'
|
||||
}))
|
||||
])
|
||||
])
|
||||
]
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListTimingsComponent {
|
||||
@Input() heroes: Hero[];
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
// #docregion
|
||||
// #docregion imports
|
||||
import {
|
||||
Component,
|
||||
Input
|
||||
} from '@angular/core';
|
||||
import {
|
||||
trigger,
|
||||
state,
|
||||
style,
|
||||
animate,
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
// #enddocregion imports
|
||||
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list-twoway',
|
||||
// #docregion template
|
||||
template: `
|
||||
<ul>
|
||||
<li *ngFor="let hero of heroes"
|
||||
[@heroState]="hero.state"
|
||||
(click)="hero.toggleState()">
|
||||
{{hero.name}}
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
// #enddocregion template
|
||||
styleUrls: ['./hero-list.component.css'],
|
||||
/*
|
||||
* Define two states, "inactive" and "active", and the end
|
||||
* styles that apply whenever the element is in those states.
|
||||
* Then define an animated transition between these two
|
||||
* states, in *both* directions.
|
||||
*/
|
||||
// #docregion animationdef
|
||||
animations: [
|
||||
trigger('heroState', [
|
||||
state('inactive', style({
|
||||
backgroundColor: '#eee',
|
||||
transform: 'scale(1)'
|
||||
})),
|
||||
state('active', style({
|
||||
backgroundColor: '#cfd8dc',
|
||||
transform: 'scale(1.1)'
|
||||
})),
|
||||
// #docregion transitions
|
||||
transition('inactive <=> active', animate('100ms ease-out'))
|
||||
// #enddocregion transitions
|
||||
])
|
||||
]
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListTwowayComponent {
|
||||
@Input() heroes: Hero[];
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
import { Hero, HeroService } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: `
|
||||
<div class="buttons">
|
||||
<button [disabled]="!heroService.canAdd()" (click)="heroService.addInactive()">Add inactive hero</button>
|
||||
<button [disabled]="!heroService.canAdd()" (click)="heroService.addActive()">Add active hero</button>
|
||||
<button [disabled]="!heroService.canRemove()" (click)="heroService.remove()">Remove hero</button>
|
||||
</div>
|
||||
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<h4>Basic State</h4>
|
||||
<p>Switch between active/inactive on click.</p>
|
||||
<app-hero-list-basic [heroes]="heroes"></app-hero-list-basic>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Styles inline in transitions</h4>
|
||||
<p>Animated effect on click, no persistend end styles.</p>
|
||||
<app-hero-list-inline-styles [heroes]="heroes"></app-hero-list-inline-styles>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Combined transition syntax</h4>
|
||||
<p>Switch between active/inactive on click. Define just one transition used in both directions.</p>
|
||||
<app-hero-list-combined-transitions [heroes]="heroes"></app-hero-list-combined-transitions>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Two-way transition syntax</h4>
|
||||
<p>Switch between active/inactive on click. Define just one transition used in both directions using the <=> syntax.</p>
|
||||
<app-hero-list-twoway [heroes]="heroes"></app-hero-list-twoway>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Enter & Leave</h4>
|
||||
<p>Enter and leave animations using the void state.</p>
|
||||
<app-hero-list-enter-leave [heroes]="heroes"></app-hero-list-enter-leave>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<h4>Enter & Leave & States</h4>
|
||||
<p>
|
||||
Enter and leave animations combined with active/inactive state animations.
|
||||
Different enter and leave transitions depending on state.
|
||||
</p>
|
||||
<app-hero-list-enter-leave-states [heroes]="heroes"></app-hero-list-enter-leave-states>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Auto Style Calc</h4>
|
||||
<p>Leave animation from the current computed height using the auto-style value *.</p>
|
||||
<app-hero-list-auto [heroes]="heroes"></app-hero-list-auto>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Different Timings</h4>
|
||||
<p>Enter and leave animations with different easings, ease-in for enter, ease-out for leave.</p>
|
||||
<app-hero-list-timings [heroes]="heroes"></app-hero-list-timings>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Multiple Keyframes</h4>
|
||||
<p>Enter and leave animations with three keyframes in each, to give the transition some bounce.</p>
|
||||
<app-hero-list-multistep [heroes]="heroes"></app-hero-list-multistep>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Parallel Groups</h4>
|
||||
<p>Enter and leave animations with multiple properties animated in parallel with different timings.</p>
|
||||
<app-hero-list-groups [heroes]="heroes"></app-hero-list-groups>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
.buttons {
|
||||
text-align: center;
|
||||
}
|
||||
button {
|
||||
padding: 1.5em 3em;
|
||||
}
|
||||
.columns {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.column {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
}
|
||||
.column p {
|
||||
min-height: 6em;
|
||||
}
|
||||
`],
|
||||
providers: [HeroService]
|
||||
})
|
||||
export class HeroTeamBuilderComponent {
|
||||
heroes: Hero[];
|
||||
|
||||
constructor(private heroService: HeroService) {
|
||||
this.heroes = heroService.heroes;
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
|
||||
// #docregion hero
|
||||
export class Hero {
|
||||
constructor(public name: string, public state = 'inactive') { }
|
||||
|
||||
toggleState() {
|
||||
this.state = this.state === 'active' ? 'inactive' : 'active';
|
||||
}
|
||||
}
|
||||
// #enddocregion hero
|
||||
|
||||
const ALL_HEROES = [
|
||||
'Windstorm',
|
||||
'RubberMan',
|
||||
'Bombasto',
|
||||
'Magneta',
|
||||
'Dynama',
|
||||
'Narco',
|
||||
'Celeritas',
|
||||
'Dr IQ',
|
||||
'Magma',
|
||||
'Tornado',
|
||||
'Mr. Nice'
|
||||
].map(name => new Hero(name));
|
||||
|
||||
@Injectable()
|
||||
export class HeroService {
|
||||
|
||||
heroes: Hero[] = [];
|
||||
|
||||
canAdd() {
|
||||
return this.heroes.length < ALL_HEROES.length;
|
||||
}
|
||||
|
||||
canRemove() {
|
||||
return this.heroes.length > 0;
|
||||
}
|
||||
|
||||
addActive(active = true) {
|
||||
let hero = ALL_HEROES[this.heroes.length];
|
||||
hero.state = active ? 'active' : 'inactive';
|
||||
this.heroes.push(hero);
|
||||
}
|
||||
|
||||
addInactive() {
|
||||
this.addActive(false);
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.heroes.length -= 1;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export class Hero {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
<p>
|
||||
Welcome to Animations in Angular!
|
||||
</p>
|
|
@ -0,0 +1,15 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
templateUrl: './home.component.html',
|
||||
styleUrls: ['./home.component.css']
|
||||
})
|
||||
export class HomeComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.insert-remove-container {
|
||||
border: 1px solid #dddddd;
|
||||
margin-top: 1em;
|
||||
padding: 20px 20px 0px 20px;
|
||||
color: #000000;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<!-- #docplaster -->
|
||||
<nav>
|
||||
<button (click)="toggle()">Toggle Insert/Remove</button>
|
||||
</nav>
|
||||
|
||||
<!-- #docregion insert-remove-->
|
||||
<div @myInsertRemoveTrigger *ngIf="isShown" class="insert-remove-container">
|
||||
<p>The box is inserted</p>
|
||||
</div>
|
||||
<!-- #enddocregion insert-remove-->
|
|
@ -0,0 +1,29 @@
|
|||
// #docplaster
|
||||
import { Component } from '@angular/core';
|
||||
import { trigger, transition, animate, style } from '@angular/animations';
|
||||
|
||||
@Component({
|
||||
selector: 'app-insert-remove',
|
||||
animations: [
|
||||
// #docregion enter-leave-trigger
|
||||
trigger('myInsertRemoveTrigger', [
|
||||
transition(':enter', [
|
||||
style({ opacity: 0 }),
|
||||
animate('5s', style({ opacity: 1 })),
|
||||
]),
|
||||
transition(':leave', [
|
||||
animate('5s', style({ opacity: 0 }))
|
||||
])
|
||||
]),
|
||||
// #enddocregion enter-leave-trigger
|
||||
],
|
||||
templateUrl: 'insert-remove.component.html',
|
||||
styleUrls: ['insert-remove.component.css']
|
||||
})
|
||||
export class InsertRemoveComponent {
|
||||
isShown = false;
|
||||
|
||||
toggle() {
|
||||
this.isShown = !this.isShown;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
// #docregion
|
||||
import { Hero } from './hero';
|
||||
|
||||
export const HEROES: Hero[] = [
|
||||
{ id: 11, name: 'Mr. Nice' },
|
||||
{ id: 12, name: 'Narco' },
|
||||
{ id: 13, name: 'Bombasto' },
|
||||
{ id: 14, name: 'Celeritas' },
|
||||
{ id: 15, name: 'Magneta' },
|
||||
{ id: 16, name: 'RubberMan' },
|
||||
{ id: 17, name: 'Dynama' },
|
||||
{ id: 18, name: 'Dr IQ' },
|
||||
{ id: 19, name: 'Magma' },
|
||||
{ id: 20, name: 'Tornado' }
|
||||
];
|
|
@ -0,0 +1,20 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-open-close-page',
|
||||
template: `
|
||||
<section>
|
||||
<h2>Open Close Component</h2>
|
||||
<input type="checkbox" [checked]="logging" (click)="toggleLogging()"/> Console Log Animation Events
|
||||
|
||||
<app-open-close [logging]="logging"></app-open-close>
|
||||
</section>
|
||||
`
|
||||
})
|
||||
export class OpenClosePageComponent {
|
||||
logging = false;
|
||||
|
||||
toggleLogging() {
|
||||
this.logging = !this.logging;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<!-- #docplaster -->
|
||||
<nav>
|
||||
<button (click)="toggle()">Toggle Open/Close</button>
|
||||
</nav>
|
||||
|
||||
<!-- #docregion compare, trigger -->
|
||||
<div [@openClose]="isOpen ? 'open' : 'closed'" class="open-close-container">
|
||||
<p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p>
|
||||
</div>
|
||||
<!-- #enddocregion compare, trigger -->
|
|
@ -0,0 +1,40 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { trigger, transition, state, animate, style, keyframes } from '@angular/animations';
|
||||
|
||||
@Component({
|
||||
selector: 'app-open-close',
|
||||
animations: [
|
||||
// #docregion trigger
|
||||
trigger('openClose', [
|
||||
state('open', style({
|
||||
height: '200px',
|
||||
opacity: 1,
|
||||
backgroundColor: 'yellow'
|
||||
})),
|
||||
state('close', style({
|
||||
height: '100px',
|
||||
opacity: 0.5,
|
||||
backgroundColor: 'green'
|
||||
})),
|
||||
// ...
|
||||
transition('* => *', [
|
||||
animate('1s', keyframes ( [
|
||||
style({ opacity: 0.1, offset: 0.1 }),
|
||||
style({ opacity: 0.6, offset: 0.2 }),
|
||||
style({ opacity: 1, offset: 0.5 }),
|
||||
style({ opacity: 0.2, offset: 0.7 })
|
||||
]))
|
||||
])
|
||||
])
|
||||
// #enddocregion trigger
|
||||
],
|
||||
templateUrl: 'open-close.component.html',
|
||||
styleUrls: ['open-close.component.css']
|
||||
})
|
||||
export class OpenCloseKeyframeComponent {
|
||||
isOpen = false;
|
||||
|
||||
toggle() {
|
||||
this.isOpen = !this.isOpen;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<!-- #docplaster -->
|
||||
<nav>
|
||||
<button (click)="toggle()">Toggle Boolean/Close</button>
|
||||
</nav>
|
||||
|
||||
<!-- #docregion trigger-boolean -->
|
||||
<div [@openClose]="isOpen ? true : false" class="open-close-container">
|
||||
<!-- #enddocregion trigger-boolean -->
|
||||
<p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p>
|
||||
<!-- #docregion trigger-boolean -->
|
||||
</div>
|
||||
<!-- #enddocregion trigger-boolean -->
|
|
@ -0,0 +1,24 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { trigger, transition, state, animate, style } from '@angular/animations';
|
||||
|
||||
@Component({
|
||||
selector: 'app-open-close-boolean',
|
||||
// #docregion trigger-boolean
|
||||
animations: [
|
||||
trigger('openClose', [
|
||||
state('true', style({ height: '*' })),
|
||||
state('false', style({ height: '0px' })),
|
||||
transition('false <=> true', animate(500))
|
||||
])
|
||||
],
|
||||
// #enddocregion trigger-boolean
|
||||
templateUrl: 'open-close.component.2.html',
|
||||
styleUrls: ['open-close.component.css']
|
||||
})
|
||||
export class OpenCloseBooleanComponent {
|
||||
isOpen = false;
|
||||
|
||||
toggle() {
|
||||
this.isOpen = !this.isOpen;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<!-- #docplaster -->
|
||||
<nav>
|
||||
<button (click)="toggle()">Toggle Open/Close</button>
|
||||
</nav>
|
||||
|
||||
<!-- #docregion callbacks -->
|
||||
<div [@openClose]="isOpen ? 'open' : 'closed'"
|
||||
(@openClose.start)="onAnimationEvent($event)"
|
||||
(@openClose.done)="onAnimationEvent($event)"
|
||||
class="open-close-container">
|
||||
<!-- #enddocregion callbacks -->
|
||||
<p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p>
|
||||
<!-- #docregion callbacks -->
|
||||
</div>
|
||||
<!-- #enddocregion callbacks -->
|
|
@ -0,0 +1,48 @@
|
|||
// #docplaster
|
||||
// #docregion reusable
|
||||
import { Component } from '@angular/core';
|
||||
import { useAnimation, transition, trigger, style, animate } from '@angular/animations';
|
||||
import { transAnimation } from './animations';
|
||||
|
||||
@Component({
|
||||
// #enddocregion reusable
|
||||
selector: 'app-open-close-reusable',
|
||||
// #docregion runtime
|
||||
animations: [
|
||||
transition('open => closed', [
|
||||
style({
|
||||
height: '200 px',
|
||||
opacity: '{{ opacity }}',
|
||||
backgroundcolor: 'yelow'
|
||||
}),
|
||||
animate('{{ time }}'),
|
||||
], {
|
||||
params: {
|
||||
time: '1s',
|
||||
opacity: '1'
|
||||
}
|
||||
}),
|
||||
// #enddocregion runtime
|
||||
// #docregion reusable
|
||||
trigger('openClose', [
|
||||
transition('open => closed', [
|
||||
useAnimation(transAnimation, {
|
||||
params: {
|
||||
height: 0,
|
||||
opacity: 1,
|
||||
backgroundColor: 'red',
|
||||
time: '1s'
|
||||
}
|
||||
})
|
||||
])
|
||||
])
|
||||
// #docregion runtime
|
||||
],
|
||||
// #enddocregion runtime
|
||||
// #enddocregion reusable
|
||||
templateUrl: 'open-close.component.html',
|
||||
styleUrls: ['open-close.component.css']
|
||||
// #docregion reusable
|
||||
})
|
||||
// #enddocregion reusable
|
||||
export class OpenCloseReusableComponent { }
|
|
@ -0,0 +1,12 @@
|
|||
<nav>
|
||||
<button (click)="toggleAnimations()">Toggle Animations</button>
|
||||
<button (click)="toggle()">Toggle Open/Closed</button>
|
||||
</nav>
|
||||
<!-- #docregion toggle-animation -->
|
||||
<div [@.disabled]="isDisabled">
|
||||
<div [@childAnimation]="isOpen ? 'open' : 'closed'"
|
||||
class="open-close-container">
|
||||
<p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- #enddocregion toggle-animation -->
|
|
@ -0,0 +1,47 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
import { trigger, transition, state, animate, style } from '@angular/animations';
|
||||
|
||||
// #docregion toggle-animation
|
||||
@Component({
|
||||
// #enddocregion toggle-animation
|
||||
selector: 'app-open-close-toggle',
|
||||
templateUrl: 'open-close.component.4.html',
|
||||
styleUrls: ['open-close.component.css'],
|
||||
// #docregion toggle-animation
|
||||
animations: [
|
||||
trigger('childAnimation', [
|
||||
// ...
|
||||
// #enddocregion toggle-animation
|
||||
state('open', style({
|
||||
width: '250px',
|
||||
opacity: 1,
|
||||
backgroundColor: 'yellow'
|
||||
})),
|
||||
state('closed', style({
|
||||
width: '100px',
|
||||
opacity: 0.5,
|
||||
backgroundColor: 'green'
|
||||
})),
|
||||
transition('* => *', [
|
||||
animate('1s')
|
||||
]),
|
||||
// #docregion toggle-animation
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class OpenCloseChildComponent {
|
||||
isDisabled = false;
|
||||
isOpen = false;
|
||||
// #enddocregion toggle-animation
|
||||
toggleAnimations() {
|
||||
this.isDisabled = !this.isDisabled;
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.isOpen = !this.isOpen;
|
||||
}
|
||||
// #docregion toggle-animation
|
||||
}
|
||||
// #enddocregion toggle-animation
|
|
@ -0,0 +1,12 @@
|
|||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.open-close-container {
|
||||
border: 1px solid #dddddd;
|
||||
margin-top: 1em;
|
||||
padding: 20px 20px 0px 20px;
|
||||
color: #000000;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<!-- #docplaster -->
|
||||
<nav>
|
||||
<button (click)="toggle()">Toggle Open/Close</button>
|
||||
</nav>
|
||||
|
||||
<!-- #docregion compare, trigger -->
|
||||
<div [@openClose]="isOpen ? 'open' : 'closed'"
|
||||
(@openClose.start)="onAnimationEvent($event)"
|
||||
(@openClose.done)="onAnimationEvent($event)"
|
||||
class="open-close-container">
|
||||
<p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p>
|
||||
</div>
|
||||
<!-- #enddocregion compare, trigger -->
|
|
@ -0,0 +1,110 @@
|
|||
// #docplaster
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { trigger, transition, state, animate, style, AnimationEvent } from '@angular/animations';
|
||||
|
||||
// #docregion component, events1
|
||||
@Component({
|
||||
selector: 'app-open-close',
|
||||
// #docregion trigger, trigger-wildcard1, trigger-transition
|
||||
animations: [
|
||||
trigger('openClose', [
|
||||
// #enddocregion events1
|
||||
// #docregion state1, events1
|
||||
// ...
|
||||
// #enddocregion events1
|
||||
state('open', style({
|
||||
height: '200px',
|
||||
opacity: 1,
|
||||
backgroundColor: 'yellow'
|
||||
})),
|
||||
// #enddocregion state1
|
||||
// #docregion state2
|
||||
state('closed', style({
|
||||
height: '100px',
|
||||
opacity: 0.5,
|
||||
backgroundColor: 'green'
|
||||
})),
|
||||
// #enddocregion state2, trigger-wildcard1
|
||||
// #docregion transition1
|
||||
transition('open => closed', [
|
||||
animate('1s')
|
||||
]),
|
||||
// #enddocregion transition1
|
||||
// #docregion transition2
|
||||
transition('closed => open', [
|
||||
animate('0.5s')
|
||||
]),
|
||||
// #enddocregion trigger, component
|
||||
// #enddocregion transition2
|
||||
// #docregion trigger-wildcard1
|
||||
transition('* => closed', [
|
||||
animate('1s')
|
||||
]),
|
||||
transition('* => open', [
|
||||
animate('0.5s')
|
||||
]),
|
||||
// #enddocregion trigger-wildcard1
|
||||
// #docregion trigger-wildcard2
|
||||
transition('open <=> closed', [
|
||||
animate('0.5s')
|
||||
]),
|
||||
// #enddocregion trigger-wildcard2
|
||||
// #docregion transition4
|
||||
transition ('* => open', [
|
||||
animate ('1s',
|
||||
style ({ opacity: '*' }),
|
||||
),
|
||||
]),
|
||||
// #enddocregion transition4
|
||||
// #docregion transition3
|
||||
transition('* => *', [
|
||||
animate('1s')
|
||||
]),
|
||||
// #enddocregion transition3, trigger-transition
|
||||
// #docregion trigger, component, trigger-wildcard1, events1
|
||||
]),
|
||||
],
|
||||
// #enddocregion trigger, trigger-wildcard1
|
||||
templateUrl: 'open-close.component.html',
|
||||
styleUrls: ['open-close.component.css']
|
||||
})
|
||||
// #docregion events
|
||||
export class OpenCloseComponent {
|
||||
// #enddocregion events1, events
|
||||
isOpen = true;
|
||||
|
||||
toggle() {
|
||||
this.isOpen = !this.isOpen;
|
||||
}
|
||||
|
||||
// #enddocregion component
|
||||
@Input() logging = false;
|
||||
// #docregion events1, events
|
||||
onAnimationEvent ( event: AnimationEvent ) {
|
||||
// #enddocregion events1, events
|
||||
if (!this.logging) {
|
||||
return;
|
||||
}
|
||||
// #docregion events
|
||||
// openClose is trigger name in this example
|
||||
console.warn(`Animation Trigger: ${event.triggerName}`);
|
||||
|
||||
// phaseName is start or done
|
||||
console.warn(`Phase: ${event.phaseName}`);
|
||||
|
||||
// in our example, totalTime is 1000 or 1 second
|
||||
console.warn(`Total time: ${event.totalTime}`);
|
||||
|
||||
// in our example, fromState is either open or closed
|
||||
console.warn(`From: ${event.fromState}`);
|
||||
|
||||
// in our example, toState either open or closed
|
||||
console.warn(`To: ${event.toState}`);
|
||||
|
||||
// the HTML element itself, the button in this case
|
||||
console.warn(`Element: ${event.element}`);
|
||||
// #docregion events1
|
||||
}
|
||||
// #docregion component
|
||||
}
|
||||
// #enddocregion component
|
|
@ -0,0 +1,12 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-status-slider-page',
|
||||
template: `
|
||||
<section>
|
||||
<h2>Status Slider</h2>
|
||||
<app-status-slider></app-status-slider>
|
||||
</section>
|
||||
`
|
||||
})
|
||||
export class StatusSliderPageComponent {}
|
|
@ -0,0 +1,13 @@
|
|||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.box {
|
||||
width: 300px;
|
||||
border: 5px solid black;
|
||||
display: block;
|
||||
line-height: 300px;
|
||||
text-align: center;
|
||||
font-size: 50px;
|
||||
color: white;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<nav>
|
||||
<button (click)="toggle()">Toggle Status</button>
|
||||
</nav>
|
||||
|
||||
<div [@slideStatus]="status" class="box">
|
||||
{{ status == 'active' ? 'Active' : 'Inactive' }}
|
||||
</div>
|
|
@ -0,0 +1,52 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { trigger, transition, state, animate, style, keyframes } from '@angular/animations';
|
||||
|
||||
@Component({
|
||||
selector: 'app-status-slider',
|
||||
templateUrl: 'status-slider.component.html',
|
||||
styleUrls: ['status-slider.component.css'],
|
||||
animations: [
|
||||
trigger('slideStatus', [
|
||||
state('inactive', style({ backgroundColor: 'blue' })),
|
||||
state('active', style({ backgroundColor: 'orange' })),
|
||||
|
||||
// #docregion keyframesWithOffsets
|
||||
transition('* => active', [
|
||||
animate('2s', keyframes([
|
||||
style({ backgroundColor: 'blue', offset: 0}),
|
||||
style({ backgroundColor: 'red', offset: 0.8}),
|
||||
style({ backgroundColor: 'orange', offset: 1.0})
|
||||
])),
|
||||
]),
|
||||
transition('* => inactive', [
|
||||
animate('2s', keyframes([
|
||||
style({ backgroundColor: 'orange', offset: 0}),
|
||||
style({ backgroundColor: 'red', offset: 0.2}),
|
||||
style({ backgroundColor: 'blue', offset: 1.0})
|
||||
]))
|
||||
]),
|
||||
// #enddocregion keyframesWithOffsets
|
||||
|
||||
// #docregion keyframes
|
||||
transition('* => active', [
|
||||
animate('2s', keyframes([
|
||||
style({ backgroundColor: 'blue' }),
|
||||
style({ backgroundColor: 'red' }),
|
||||
style({ backgroundColor: 'orange' })
|
||||
]))
|
||||
// #enddocregion keyframes
|
||||
]),
|
||||
])
|
||||
]
|
||||
})
|
||||
export class StatusSliderComponent {
|
||||
status: 'active' | 'inactive' = 'inactive';
|
||||
|
||||
toggle() {
|
||||
if (this.status === 'active') {
|
||||
this.status = 'inactive';
|
||||
} else {
|
||||
this.status = 'active';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-toggle-animations-child-page',
|
||||
template: `
|
||||
<section>
|
||||
<h2>Toggle Animations</h2>
|
||||
|
||||
<app-open-close-toggle></app-open-close-toggle>
|
||||
</section>
|
||||
`
|
||||
})
|
||||
export class ToggleAnimationsPageComponent {}
|
|
@ -8,12 +8,7 @@
|
|||
</head>
|
||||
|
||||
<body>
|
||||
<h1 style="visibility: hidden;">External H1 Title for E2E test</h1>
|
||||
<app-root></app-root>
|
||||
<button style="visibility: hidden;">External button for E2E test</button>
|
||||
<ul style="visibility: hidden;">
|
||||
<li>External list for E2E test</li>
|
||||
</ul>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
{
|
||||
"description": "Angular Animations",
|
||||
"description": "Angular Animations Guide",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js"
|
||||
]
|
||||
"!**/*.js",
|
||||
"!**/*.[1,2,3].*"
|
||||
],
|
||||
"tags": ["animations"]
|
||||
}
|
||||
|
|
|
@ -1,354 +1,334 @@
|
|||
# Animations
|
||||
# Introduction to Angular animations
|
||||
|
||||
Motion is an important aspect in the design of modern web applications. Good
|
||||
user interfaces transition smoothly between states with engaging animations
|
||||
that call attention where it's needed. Well-designed animations can make a UI not only
|
||||
more fun but also easier to use.
|
||||
Animation provides the illusion of motion: HTML elements change styling over time. Well-designed animations can make your application more fun and easier to use, but they aren't just cosmetic. Animations can improve your app and user experience in a number of ways:
|
||||
|
||||
## Overview
|
||||
* Without animations, web page transitions can seem abrupt and jarring.
|
||||
|
||||
Angular's animation system lets you build animations that run with the same kind of native
|
||||
performance found in pure CSS animations. You can also tightly integrate your
|
||||
animation logic with the rest of your application code, for ease of control.
|
||||
* Motion greatly enhances the user experience, so animations give users a chance to detect the application's response to their actions.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
* Good animations intuitively call the user's attention to where it is needed.
|
||||
|
||||
Angular animations are built on top of the standard [Web Animations API](https://w3c.github.io/web-animations/)
|
||||
and run natively on [browsers that support it](http://caniuse.com/#feat=web-animation).
|
||||
Typically, animations involve multiple style *transformations* over time. An HTML element can move, change color, grow or shrink, fade, or slide off the page. These changes can occur simultaneously or sequentially. You can control the timing of each transformation.
|
||||
|
||||
As of Angular 6, If the Web Animations API is not supported natively by the browser, then Angular will use CSS
|
||||
keyframes as a fallback instead (automatically). This means that the polyfill is no longer required unless any
|
||||
code uses [AnimationBuilder](/api/animations/AnimationBuilder). If your code does use AnimationBuilder, then
|
||||
uncomment the `web-animations-js` polyfill from the `polyfills.ts` file generated by Angular CLI.
|
||||
</div>
|
||||
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](https://www.w3.org/TR/css-transitions-1/) page.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
The examples in this page are available as a <live-example></live-example>.
|
||||
## About this guide
|
||||
|
||||
</div>
|
||||
This guide covers the basic Angular animation features to get you started on adding Angular animations to your project.
|
||||
|
||||
## Setup
|
||||
The features described in this guide — and the more advanced features described in the related Angular animations guides — are demonstrated in an example app available as a <live-example></live-example>.
|
||||
|
||||
Before you can add animations to your application, you need
|
||||
to import a few animation-specific modules and functions to the root application module.
|
||||
#### Prerequisites
|
||||
|
||||
<code-example path="animations/src/app/app.module.ts" region="animations-module" title="app.module.ts (animation module import excerpt)" linenums="false"></code-example>
|
||||
The guide assumes that you're familiar with building basic Angular apps, as described in the following sections:
|
||||
|
||||
#### Example basics
|
||||
* [Tutorial](tutorial)
|
||||
* [Architecture Overview](guide/architecture)
|
||||
|
||||
The animations examples in this guide animate a list of heroes.
|
||||
|
||||
A `Hero` class has a `name` property, a `state` property that indicates if the hero is active or not,
|
||||
and a `toggleState()` method to switch between the states.
|
||||
## Getting started
|
||||
|
||||
<code-example path="animations/src/app/hero.service.ts" region="hero" title="hero.service.ts (Hero class)" linenums="false"></code-example>
|
||||
The main Angular modules for animations are `@angular/animations` and `@angular/platform-browser`. When you create a new project using the CLI, these dependencies are automatically added to your project.
|
||||
|
||||
Across the top of the screen (`app.hero-team-builder.component.ts`)
|
||||
are a series of buttons that add and remove heroes from the list (via the `HeroService`).
|
||||
The buttons trigger changes to the list that all of the example components see at the same time.
|
||||
To get started with adding Angular animations to your project, import the animation-specific modules along with standard Angular functionality.
|
||||
|
||||
{@a example-transitioning-between-states}
|
||||
### Step 1: Enabling the animations module
|
||||
|
||||
## Transitioning between two states
|
||||
Import `BrowserAnimationsModule`, which introduces the animation capabilities into your Angular root application module.
|
||||
|
||||
<img src="generated/images/guide/animations/animation_basic_click.gif" alt="A simple transition animation" class="right">
|
||||
|
||||
You can build a simple animation that transitions an element between two states
|
||||
driven by a model attribute.
|
||||
|
||||
|
||||
Animations can be defined inside `@Component` metadata.
|
||||
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" region="imports" title="hero-list-basic.component.ts" linenums="false"></code-example>
|
||||
|
||||
With these, you can define an *animation trigger* called `heroState` in the component
|
||||
metadata. It uses animations to transition between two states: `active` and `inactive`. When a
|
||||
hero is active, the element appears in a slightly larger size and lighter color.
|
||||
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" region="animationdef" title="hero-list-basic.component.ts (@Component excerpt)" linenums="false"></code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
In this example, you are defining animation styles (color and transform) inline in the
|
||||
animation metadata.
|
||||
|
||||
</div>
|
||||
|
||||
Now, using the `[@triggerName]` syntax, attach the animation that you just defined to
|
||||
one or more elements in the component's template.
|
||||
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" region="template" title="hero-list-basic.component.ts (excerpt)" linenums="false"></code-example>
|
||||
|
||||
Here, the animation trigger applies to every element repeated by an `ngFor`. Each of
|
||||
the repeated elements animates independently. The value of the
|
||||
attribute is bound to the expression `hero.state` and is always either `active` or `inactive`.
|
||||
|
||||
With this setup, an animated transition appears whenever a hero object changes state.
|
||||
Here's the full component implementation:
|
||||
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" title="hero-list-basic.component.ts"></code-example>
|
||||
|
||||
## States and transitions
|
||||
|
||||
Angular animations are defined as logical **states** and **transitions**
|
||||
between states.
|
||||
|
||||
An animation state is a string value that you define in your application code. In the example
|
||||
above, the states `'active'` and `'inactive'` are based on the logical state of
|
||||
hero objects. The source of the state can be a simple object attribute, as it was in this case,
|
||||
or it can be a value computed in a method. The important thing is that you can read it into the
|
||||
component's template.
|
||||
|
||||
You can define *styles* for each animation state:
|
||||
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" region="states" title="src/app/hero-list-basic.component.ts" linenums="false"></code-example>
|
||||
|
||||
These `state` definitions specify the *end styles* of each state.
|
||||
They are applied to the element once it has transitioned to that state, and stay
|
||||
*as long as it remains in that state*. In effect, you're defining what styles the element has in different states.
|
||||
|
||||
After you define states, you can define *transitions* between the states. Each transition
|
||||
controls the timing of switching between one set of styles and the next:
|
||||
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" region="transitions" title="src/app/hero-list-basic.component.ts" linenums="false"></code-example>
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/animations/ng_animate_transitions_inactive_active.png" alt="In Angular animations you define states and transitions between states" width="400">
|
||||
</figure>
|
||||
|
||||
If several transitions have the same timing configuration, you can combine
|
||||
them into the same `transition` definition:
|
||||
|
||||
<code-example path="animations/src/app/hero-list-combined-transitions.component.ts" region="transitions" title="src/app/hero-list-combined-transitions.component.ts" linenums="false"></code-example>
|
||||
|
||||
When both directions of a transition have the same timing, as in the previous
|
||||
example, you can use the shorthand syntax `<=>`:
|
||||
|
||||
<code-example path="animations/src/app/hero-list-twoway.component.ts" region="transitions" title="src/app/hero-list-twoway.component.ts" linenums="false"></code-example>
|
||||
|
||||
You can also apply a style during an animation but not keep it around
|
||||
after the animation finishes. You can define such styles inline, in the `transition`. In this example,
|
||||
the element receives one set of styles immediately and is then animated to the next.
|
||||
When the transition finishes, none of these styles are kept because they're not
|
||||
defined in a `state`.
|
||||
|
||||
<code-example path="animations/src/app/hero-list-inline-styles.component.ts" region="transitions" title="src/app/hero-list-inline-styles.component.ts" linenums="false"></code-example>
|
||||
|
||||
### The wildcard state `*`
|
||||
|
||||
The `*` ("wildcard") state matches *any* animation state. This is useful for defining styles and
|
||||
transitions that apply regardless of which state the animation is in. For example:
|
||||
|
||||
* The `active => *` transition applies when the element's state changes from `active` to anything else.
|
||||
* The `* => *` transition applies when *any* change between two states takes place.
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/animations/ng_animate_transitions_inactive_active_wildcards.png" alt="The wildcard state can be used to match many different transitions at once" width="400">
|
||||
</figure>
|
||||
|
||||
### The `void` state
|
||||
|
||||
The special state called `void` can apply to any animation. It applies
|
||||
when the element is *not* attached to a view, perhaps because it has not yet been
|
||||
added or because it has been removed. The `void` state is useful for defining enter and
|
||||
leave animations.
|
||||
|
||||
For example the `* => void` transition applies when the element leaves the view,
|
||||
regardless of what state it was in before it left.
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/animations/ng_animate_transitions_void_in.png" alt="The void state can be used for enter and leave transitions" width="400">
|
||||
</figure>
|
||||
|
||||
The wildcard state `*` also matches `void`.
|
||||
|
||||
## Example: Entering and leaving
|
||||
|
||||
<img src="generated/images/guide/animations/animation_enter_leave.gif" alt="Enter and leave animations" class="right" width="250">
|
||||
|
||||
Using the `void` and `*` states you can define transitions that animate the
|
||||
entering and leaving of elements:
|
||||
|
||||
* Enter: `void => *`
|
||||
* Leave: `* => void`
|
||||
|
||||
For example, in the `animations` array below there are two transitions that use
|
||||
the `void => *` and `* => void` syntax to animate the element in and out of the view.
|
||||
|
||||
<code-example path="animations/src/app/hero-list-enter-leave.component.ts" region="animationdef" title="hero-list-enter-leave.component.ts (excerpt)" linenums="false"></code-example>
|
||||
|
||||
Note that in this case the styles are applied to the void state directly in the
|
||||
transition definitions, and not in a separate `state(void)` definition. Thus, the transforms
|
||||
are different on enter and leave: the element enters from the left
|
||||
and leaves to the right.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
These two common animations have their own aliases:
|
||||
|
||||
<code-example language="typescript">
|
||||
transition(':enter', [ ... ]); // void => *
|
||||
transition(':leave', [ ... ]); // * => void
|
||||
<code-example path="animations/src/app/app.module.1.ts" title="src/app/app.module.ts" language="typescript" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**Note:** When you use the CLI to create your app, the root application module `app.module.ts` is placed in the `src/app` folder.
|
||||
</div>
|
||||
|
||||
## Example: Entering and leaving from different states
|
||||
### Step 2: Importing animation functions into component files
|
||||
|
||||
<img src="generated/images/guide/animations/animation_enter_leave_states.gif" alt="Enter and leave animations combined with state animations" class="right" width="200">
|
||||
If you plan to use specific animation functions in component files, import those functions from `@angular/animations`.
|
||||
|
||||
You can also combine this animation with the earlier state transition animation by
|
||||
using the hero state as the animation state. This lets you configure
|
||||
different transitions for entering and leaving based on what the state of the hero
|
||||
is:
|
||||
<code-example path="animations/src/app/app.component.ts" title="src/app/app.component.ts" region="imports" language="typescript">
|
||||
</code-example>
|
||||
|
||||
* Inactive hero enter: `void => inactive`
|
||||
* Active hero enter: `void => active`
|
||||
* Inactive hero leave: `inactive => void`
|
||||
* Active hero leave: `active => void`
|
||||
<div class="alert is-helpful">
|
||||
|
||||
This gives you fine-grained control over each transition:
|
||||
**Note:** See a [summary of available animation functions](guide/animations#animation-api-summary) at the end of this guide.
|
||||
</div>
|
||||
|
||||
### Step 3: Adding the animation metadata property
|
||||
|
||||
In the component file, add a metadata property called `animations:` within the `@Component()` decorator. You put the trigger that defines an animation within the `animations` metadata property.
|
||||
|
||||
<code-example path="animations/src/app/app.component.ts" title="src/app/app.component.ts" region="decorator" language="typescript">
|
||||
</code-example>
|
||||
|
||||
## Animating a simple transition
|
||||
|
||||
Let's animate a simple transition that changes a single HTML element from one state to another. For example, you can specify that a button displays either **Open** or **Closed** based on the user's last action. When the button is in the `open` state, it's visible and yellow. When it's the `closed` state, it's transparent and green.
|
||||
|
||||
In HTML, these attributes are set using ordinary CSS styles such as color and opacity. In Angular, use the `style()` function to specify a set of CSS styles for use with animations. You can collect a set of styles in an animation state, and give the state a name, such as `open` or `closed`.
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/animations/ng_animate_transitions_inactive_active_void.png" alt="This example transitions between active, inactive, and void states" width="400">
|
||||
<img src="generated/images/guide/animations/open-closed.png" alt="open and closed states">
|
||||
</figure>
|
||||
|
||||
<code-example path="animations/src/app/hero-list-enter-leave-states.component.ts" region="animationdef" title="hero-list-enter-leave.component.ts (excerpt)" linenums="false"></code-example>
|
||||
### Animation state and styles
|
||||
|
||||
## Animatable properties and units
|
||||
Use Angular's `state()` function to define different states to call at the end of each transition. This function takes two arguments: a unique name like `open` or `closed` and a `style()` function.
|
||||
|
||||
Since Angular's animation support builds on top of Web Animations, you can animate any property
|
||||
that the browser considers *animatable*. This includes positions, sizes, transforms, colors,
|
||||
borders, and many others. The W3C maintains
|
||||
[a list of animatable properties](https://www.w3.org/TR/css3-transitions/#animatable-properties)
|
||||
on its [CSS Transitions page](https://www.w3.org/TR/css3-transitions).
|
||||
Use the `style()` function to define a set of styles to associate with a given state name. Note that the style attributes must be in [*camelCase*](guide/glossary#case-conventions).
|
||||
|
||||
For positional properties that have a numeric value, you can define a unit by providing
|
||||
the value as a string with the appropriate suffix:
|
||||
Let's see how Angular's `state()` function works with the `style()` function to set CSS style attributes. In this code snippet, multiple style attributes are set at the same time for the state. In the `open` state, the button has a height of 200 pixels, an opacity of 1, and a background color of yellow.
|
||||
|
||||
* `'50px'`
|
||||
* `'3em'`
|
||||
* `'100%'`
|
||||
<code-example path="animations/src/app/open-close.component.ts" title="src/app/open-close.component.ts" region="state1" language="typescript">
|
||||
</code-example>
|
||||
|
||||
If you don't provide a unit when specifying dimension, Angular assumes the default of `px`:
|
||||
In the `closed` state, shown below, the button has a height of 100 pixels, an opacity of 0.5, and a background color of green.
|
||||
|
||||
* `50` is the same as saying `'50px'`
|
||||
<code-example path="animations/src/app/open-close.component.ts" title="src/app/open-close.component.ts" region="state2" language="typescript">
|
||||
</code-example>
|
||||
|
||||
## Automatic property calculation
|
||||
### Transitions and timing
|
||||
|
||||
<img src="generated/images/guide/animations/animation_auto.gif" alt="Animation with automated height calculation" class="right" width="220">
|
||||
In Angular, you can set multiple styles without any animation. However, without further refinement, the button instantly transforms with no fade, no shrinkage, or other visible indicator that a change is occurring.
|
||||
|
||||
Sometimes you don't know the value of a dimensional style property until runtime.
|
||||
For example, elements often have widths and heights that
|
||||
depend on their content and the screen size. These properties are often tricky
|
||||
to animate with CSS.
|
||||
To make the change less abrupt, we need to define an animation *transition* to specify the changes that occur between one state and another over a period of time. The `transition()` function accepts two arguments: the first argument accepts an expression that defines the direction between two transition states, and the second argument accepts an `animate()` function.
|
||||
|
||||
In these cases, you can use a special `*` property value so that the value of the
|
||||
property is computed at runtime and then plugged into the animation.
|
||||
|
||||
In this example, the leave animation takes whatever height the element has before it
|
||||
leaves and animates from that height to zero:
|
||||
Use the `animate()` function to define the length, delay, and easing of a transition, and to designate the style function for defining styles while transitions are taking place. You can also use the `animate()` function to define the `keyframes()` function for multi-step animations. These definitions are placed in the second argument of the `animate()` function.
|
||||
|
||||
<code-example path="animations/src/app/hero-list-auto.component.ts" region="animationdef" title="src/app/hero-list-auto.component.ts" linenums="false"></code-example>
|
||||
#### Animation metadata: duration, delay, and easing
|
||||
|
||||
## Animation timing
|
||||
The `animate()` function (second argument of the transition function) accepts the `timings` and `styles` input parameters.
|
||||
|
||||
There are three timing properties you can tune for every animated transition:
|
||||
the duration, the delay, and the easing function. They are all combined into
|
||||
a single transition *timing string*.
|
||||
The `timings` parameter takes a string defined in three parts.
|
||||
|
||||
### Duration
|
||||
>`animate ('duration delay easing')`
|
||||
|
||||
The duration controls how long the animation takes to run from start to finish.
|
||||
You can define a duration in three ways:
|
||||
The first part, `duration`, is required. The duration can be expressed in milliseconds as a simple number without quotes, or in seconds with quotes and a time specifier. For example, a duration of a tenth of a second can be expressed as follows:
|
||||
|
||||
* As a plain number, in milliseconds: `100`
|
||||
|
||||
* In a string, as milliseconds: `'100ms'`
|
||||
|
||||
* In a string, as seconds: `'0.1s'`
|
||||
|
||||
### Delay
|
||||
|
||||
The delay controls the length of time between the animation trigger and the beginning
|
||||
of the transition. You can define one by adding it to the same string
|
||||
following the duration. It also has the same format options as the duration:
|
||||
The second argument, `delay`, has the same syntax as `duration`. For example:
|
||||
|
||||
* Wait for 100ms and then run for 200ms: `'0.2s 100ms'`
|
||||
|
||||
### Easing
|
||||
The third argument, `easing`, controls how the animation [accelerates and decelerates](http://easings.net/) during its runtime. For example, `ease-in` causes the animation to begin slowly, and to pick up speed as it progresses.
|
||||
|
||||
The [easing function](http://easings.net/) controls how the animation accelerates
|
||||
and decelerates during its runtime. For example, an `ease-in` function causes
|
||||
the animation to begin relatively slowly but pick up speed as it progresses. You
|
||||
can control the easing by adding it as a *third* value in the string after the duration
|
||||
and the delay (or as the *second* value when there is no delay):
|
||||
* Wait for 100ms, run for 200ms. Use a deceleration curve to start out fast and slowly decelerate to a resting point: `'0.2s 100ms ease-out'`
|
||||
|
||||
* Wait for 100ms and then run for 200ms, with easing: `'0.2s 100ms ease-out'`
|
||||
* Run for 200ms, with easing: `'0.2s ease-in-out'`
|
||||
* Run for 200ms, with no delay. Use a standard curve to start slow, accelerate in the middle, and then decelerate slowly at the end: `'0.2s ease-in-out'`
|
||||
|
||||
<img src="generated/images/guide/animations/animation_timings.gif" alt="Animations with specific timings" class="right" width="220">
|
||||
* Start immediately, run for 200ms. Use a acceleration curve to start slow and end at full velocity: `'0.2s ease-in'`
|
||||
|
||||
### Example
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Here are a couple of custom timings in action. Both enter and leave last for
|
||||
200 milliseconds, that is `0.2s`, but they have different easings. The leave begins after a
|
||||
slight delay of 10 milliseconds as specified in `'0.2s 10 ease-out'`:
|
||||
**Note:** See the Angular Material Design website's topic on [Natural easing curves](https://material.io/design/motion/speed.html#easing) for general information on easing curves.
|
||||
</div>
|
||||
|
||||
<code-example path="animations/src/app/hero-list-timings.component.ts" region="animationdef" title="hero-list-timings.component.ts (excerpt)" linenums="false"></code-example>
|
||||
This example provides a state transition from `open` to `closed` with a one second transition between states.
|
||||
|
||||
## Multi-step animations with keyframes
|
||||
<code-example path="animations/src/app/open-close.component.ts" title="src/app/open-close.component.ts" language="typescript"
|
||||
region="transition1">
|
||||
</code-example>
|
||||
|
||||
<img src="generated/images/guide/animations/animation_multistep.gif" alt="Animations with some bounce implemented with keyframes" class="right" width="220">
|
||||
In the code snippet above, the `=>` operator indicates unidirectional transitions, and `<=>` is bidirectional. Within the transition, `animate()` specifies how long the transition takes. In this case, the state change from `open` to `closed` takes one second, expressed here as `1s`.
|
||||
|
||||
Animation *keyframes* go beyond a simple transition to a more intricate animation
|
||||
that goes through one or more intermediate styles when transitioning between two sets of styles.
|
||||
This example adds a state transition from the `closed` state to the `open` state with a 0.5 second transition animation arc.
|
||||
|
||||
For each keyframe, you specify an *offset* that defines at which point
|
||||
in the animation that keyframe applies. The offset is a number between zero,
|
||||
which marks the beginning of the animation, and one, which marks the end.
|
||||
<code-example path="animations/src/app/open-close.component.ts" title="src/app/open-close.component.ts" language="typescript"
|
||||
region="transition2">
|
||||
</code-example>
|
||||
|
||||
This example adds some "bounce" to the enter and leave animations with
|
||||
keyframes:
|
||||
<div class="alert is-helpful">
|
||||
|
||||
<code-example path="animations/src/app/hero-list-multistep.component.ts" region="animationdef" title="hero-list-multistep.component.ts (excerpt)" linenums="false"></code-example>
|
||||
**Note:** Some additional notes on using styles within `state` and `transition` functions.
|
||||
|
||||
Note that the offsets are *not* defined in terms of absolute time. They are relative
|
||||
measures from zero to one. The final timeline of the animation is based on the combination
|
||||
of keyframe offsets, duration, delay, and easing.
|
||||
* Use `state()` to define styles that are applied at the end of each transition, they persist after the animation has completed.
|
||||
|
||||
Defining offsets for keyframes is optional. If you omit them, offsets with even
|
||||
spacing are automatically assigned. For example, three keyframes without predefined
|
||||
offsets receive offsets `0`, `0.5`, and `1`.
|
||||
* Use `transition()` to define intermediate styles, which create the illusion of motion during the animation.
|
||||
|
||||
## Parallel animation groups
|
||||
* When animations are disabled, `transition()` styles can be skipped, but `state()` styles can't.
|
||||
|
||||
<img src="generated/images/guide/animations/animation_groups.gif" alt="Parallel animations with different timings, implemented with groups" class="right" width="220px">
|
||||
* You can include multiple state pairs within the same `transition()` argument:<br/> `transition( 'on => off, off => void' )`.
|
||||
</div>
|
||||
|
||||
You've seen how to animate multiple style properties at the same time:
|
||||
just put all of them into the same `style()` definition.
|
||||
### Triggering the animation
|
||||
|
||||
But you may also want to configure different *timings* for animations that happen
|
||||
in parallel. For example, you may want to animate two CSS properties but use a
|
||||
different easing function for each one.
|
||||
An animation requires a *trigger*, so that it knows when to start. The `trigger()` function collects the states and transitions, and gives the animation a name, so that you can attach it to the triggering element in the HTML template.
|
||||
|
||||
For this you can use animation *groups*. In this example, using groups both on
|
||||
enter and leave allows for two different timing configurations. Both
|
||||
are applied to the same element in parallel, but run independently of each other:
|
||||
The `trigger()` function describes the property name to watch for changes. When a change occurs, the trigger initiates the actions included in its definition. These actions can be transitions or other functions, as we'll see later on.
|
||||
|
||||
<code-example path="animations/src/app/hero-list-groups.component.ts" region="animationdef" title="hero-list-groups.component.ts (excerpt)" linenums="false"></code-example>
|
||||
In this example, we'll name the trigger `openClose`, and attach it to the `button` element. The trigger describes the open and closed states, and the timings for the two transitions.
|
||||
|
||||
One group animates the element transform and width; the other group animates the opacity.
|
||||
<figure>
|
||||
<img src="generated/images/guide/animations/triggering-the-animation.png" alt="triggering the animation">
|
||||
</figure>
|
||||
|
||||
## Animation callbacks
|
||||
<div class="alert is-helpful">
|
||||
|
||||
A callback is fired when an animation is started and also when it is done.
|
||||
**Note:** Within each `trigger()` function call, an element can only be in one state at any given time. However, it's possible for multiple triggers to be active at once.
|
||||
</div>
|
||||
|
||||
In the keyframes example, you have a `trigger` called `@flyInOut`. You can hook
|
||||
those callbacks like this:
|
||||
### Defining animations and attaching them to the HTML template
|
||||
|
||||
<code-example path="animations/src/app/hero-list-multistep.component.ts" region="template" title="hero-list-multistep.component.ts (excerpt)" linenums="false"></code-example>
|
||||
Animations are defined in the metadata of the component that controls the HTML element to be animated. Put the code that defines your animations under the `animations:` property within the `@Component()` decorator.
|
||||
|
||||
The callbacks receive an `AnimationEvent` that contains useful properties such as
|
||||
`fromState`, `toState` and `totalTime`.
|
||||
<code-example path="animations/src/app/open-close.component.ts" title="src/app/open-close.component.ts" language="typescript"
|
||||
region="component" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Those callbacks will fire whether or not an animation is picked up.
|
||||
When you've defined an animation trigger for a component, you can attach it to an element in that component's template by wrapping the trigger name in brackets and preceding it with an `@` symbol. Then, you can bind the trigger to a template expression using standard Angular property binding syntax as shown below, where `triggerName` is the name of the trigger, and `expression` evaluates to a defined animation state.
|
||||
|
||||
```
|
||||
<div [@triggerName]="expression">...</div>;
|
||||
```
|
||||
|
||||
The animation is executed or triggered when the expression value changes to a new state.
|
||||
|
||||
The following code snippet binds the trigger to the value of the `isOpen` property.
|
||||
|
||||
<code-example path="animations/src/app/open-close.component.1.html" title="src/app/open-close.component.html"
|
||||
region="compare">
|
||||
</code-example>
|
||||
|
||||
In this example, when the `isOpen` expression evaluates to a defined state of `open` or `closed`, it notifies the trigger `openClose` of a state change. Then it's up to the `openClose` code to handle the state change and kick off a state change animation.
|
||||
|
||||
For elements entering or leaving a page (inserted or removed from the DOM), you can make the animations conditional. For example, use `*ngIf` with the animation trigger in the HTML template.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**Note:** In the component file, set the trigger that defines the animations as the value of the `animations:` property in the `@Component()` decorator.
|
||||
|
||||
In the HTML template file, use the trigger name to attach the defined animations to the HTML element to be animated.
|
||||
|
||||
</div>
|
||||
|
||||
### Code review
|
||||
|
||||
Here are the code files discussed in the transition example.
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/open-close.component.ts" path="animations/src/app/open-close.component.ts" language="typescript"
|
||||
region="component">
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="src/app/open-close.component.html" path="animations/src/app/open-close.component.1.html"
|
||||
region="trigger">
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="src/app/open-close.component.css" path="animations/src/app/open-close.component.css">
|
||||
</code-pane>
|
||||
|
||||
</code-tabs>
|
||||
|
||||
### Summary
|
||||
|
||||
You learned to add animation to a simple transition between two states, using `style()` and `state()` along with `animate()` for the timing.
|
||||
|
||||
You can learn about more advanced features in Angular animations under the Animation section, beginning with advanced techniques in [transition and triggers](guide/transition-and-triggers).
|
||||
|
||||
{@a animation-api-summary}
|
||||
## Animations API summary
|
||||
|
||||
The functional API provided by the `@angular/animations` module provides a domain-specific language (DSL) for creating and controlling animations in Angular applications. See the [API reference](api/animations) for a complete listing and syntax details of the core functions and related data structures.
|
||||
|
||||
<table>
|
||||
|
||||
<tr>
|
||||
<th style="vertical-align: top">
|
||||
Function name
|
||||
</th>
|
||||
|
||||
<th style="vertical-align: top">
|
||||
What it does
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>trigger()</code></td>
|
||||
<td>Kicks off the animation and serves as a container for all other animation function calls. HTML template binds to <code>triggerName</code>. Use the first argument to declare a unique trigger name. Uses array syntax.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>style()</code></td>
|
||||
<td>Defines one or more CSS styles to use in animations. Controls the visual appearance of HTML elements during animations. Uses object syntax.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>state()</code></td>
|
||||
<td>Creates a named set of CSS styles that should be applied on successful transition to a given state. The state can then be referenced by name within other animation functions.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>animate()</code></td>
|
||||
<td>Specifies the timing information for a transition. Optional values for <code>delay</code> and <code>easing</code>. Can contain <code>style()</code> calls within.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>transition()</code></td>
|
||||
<td>Defines the animation sequence between two named states. Uses array syntax.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>keyframes()</code></td>
|
||||
<td>Allows a sequential change between styles within a specified time interval. Use within <code>animate()</code>. Can include multiple <code>style()</code> calls within each <code>keyframe()</code>. Uses array syntax.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>group()</code></td>
|
||||
<td>Specifies a group of animation steps (<em>inner animations</em>) to be run in parallel. Animation continues only after all inner animation steps have completed. Used within <code>sequence()</code> or <code>transition().</code></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>query()</code></td>
|
||||
<td>Use to find one or more inner HTML elements within the current element. </td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>sequence()</code></td>
|
||||
<td>Specifies a list of animation steps that are run sequentially, one by one.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>stagger()</code></td>
|
||||
<td>Staggers the starting time for animations for multiple elements.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>animation()</code></td>
|
||||
<td>Produces a reusable animation that can be invoked from elsewhere. Used together with <code>useAnimation()</code>.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>useAnimation()</code></td>
|
||||
<td>Activates a reusable animation. Used with <code>animation()</code>.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>animateChild()</code></td>
|
||||
<td>Allows animations on child components to be run within the same timeframe as the parent.</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
## More on Angular animations
|
||||
|
||||
You may also be interested in the following:
|
||||
|
||||
* [Transition and triggers](guide/transition-and-triggers)
|
||||
* [Complex animation sequences](guide/complex-animation-sequences)
|
||||
* [Reusable animations](guide/reusable-animations)
|
||||
* [Route transition animations](guide/route-animations)
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Check out this full animation [demo](http://animationsftw.in/#/) with accompanying [presentation](https://www.youtube.com/watch?v=JhNo3Wvj6UQ&feature=youtu.be&t=2h47m53s), shown at the AngularConnect conference in November 2017.
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
# Complex animation sequences
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
A basic understanding of the following concepts:
|
||||
|
||||
* [Introduction to Angular animations](guide/animations)
|
||||
* [Transition and triggers](guide/transition-and-triggers)
|
||||
|
||||
<hr>
|
||||
|
||||
So far, we've learned simple animations of single HTML elements. Angular also lets you animate coordinated sequences, such as an entire grid or list of elements as they enter and leave a page. You can choose to run multiple animations in parallel, or run discrete animations sequentially, one following another.
|
||||
|
||||
Functions that control complex animation sequences are as follows:
|
||||
|
||||
* `query()` finds one or more inner HTML elements.
|
||||
* `stagger()` applies a cascading delay to animations for multiple elements.
|
||||
* `group()` runs multiple animation steps in parallel.
|
||||
* `sequence()` runs animation steps one after another.
|
||||
|
||||
{@a complex-sequence}
|
||||
|
||||
## Animate multiple elements using query() and stagger() functions
|
||||
|
||||
The `query()` function allows you to 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 allows you to define a timing gap between each queried item that is animated and thus animates elements with a delay between them.
|
||||
|
||||
The Filter/Stagger tab in the live example shows a list of heroes with an introductory sequence. The entire list of heroes cascades in, with a slight delay from top to bottom.
|
||||
|
||||
The following example demonstrates how to use `query()` and `stagger()` functions on the entry of an animated element.
|
||||
|
||||
* Use `query()` to look for any element entering or leaving the page. The query specifies elements meeting certain CSS class criteria.
|
||||
|
||||
* For each of these elements, use `style()` to set the same initial style for the element. Make it invisible and use `transform` to move it out of position so that it can slide into place.
|
||||
|
||||
* Use `stagger()` to delay each animation by 30 milliseconds.
|
||||
|
||||
* Animate each element on screen for 0.5 seconds using a custom-defined easing curve, simultaneously fading it in and un-transforming it.
|
||||
|
||||
<code-example path="animations/src/app/hero-list-page.component.ts" title="src/app/hero-list-page.component.ts" region="page-animations" language="typescript" linenums="false"></code-example>
|
||||
|
||||
## Parallel animation using group() function
|
||||
|
||||
You've seen how to add a delay between each successive animation. But you may also want to configure animations that happen in parallel. For example, you may want to animate two CSS properties of the same element but use a different `easing` function for each one. For this, you can use the animation `group()` function.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**Note:** The `group()` function is used to group animation *steps*, rather than animated elements.
|
||||
</div>
|
||||
|
||||
In the following example, using groups on both `:enter` and `:leave` allow for two different timing configurations. They're applied to the same element in parallel, but run independently.
|
||||
|
||||
<code-example path="animations/src/app/hero-list-groups.component.ts" region="animationdef" title="src/app/hero-list-groups.component.ts (excerpt)" language="typescript" linenums="false"></code-example>
|
||||
|
||||
## Sequential vs. parallel animations
|
||||
|
||||
Complex animations can have many things happening at once. But what if you want to create an animation involving several animations happening one after the other? Earlier we used `group()` to run multiple animations all at the same time, in parallel.
|
||||
|
||||
A second function called `sequence()` lets you run those same animations one after the other. Within `sequence()`, the animation steps consist of either `style()` or `animate()` function calls.
|
||||
|
||||
* Use `style()` to apply the provided styling data immediately.
|
||||
* Use `animate()` to apply styling data over a given time interval.
|
||||
|
||||
## Filter animation example
|
||||
|
||||
Let's take a look at another animation on the live example page. Under the Filter/Stagger tab, enter some text into the **Search Heroes** text box, such as `Magnet` or `tornado`.
|
||||
|
||||
The filter works in real time as you type. Elements leave the page as you type each new letter and the filter gets progressively stricter. The heroes list gradually re-enters the page as you delete each letter in the filter box.
|
||||
|
||||
The HTML template contains a trigger called `filterAnimation`.
|
||||
|
||||
<code-example path="animations/src/app/hero-list-page.component.html" title="src/app/hero-list-page.component.html" region="filter-animations"></code-example>
|
||||
|
||||
The component file contains three transitions.
|
||||
|
||||
<code-example path="animations/src/app/hero-list-page.component.ts" title="src/app/hero-list-page.component.ts" region="filter-animations" language="typescript" linenums="false"></code-example>
|
||||
|
||||
The animation does the following:
|
||||
|
||||
* Ignores any animations that are performed when the user first opens or navigates to this page. The filter narrows what is already there, so it assumes that any HTML elements to be animated already exist in the DOM.
|
||||
|
||||
* Performs a filter match for matches.
|
||||
|
||||
For each match:
|
||||
|
||||
* Hides the element by making it completely transparent and infinitely narrow, by setting its opacity and width to 0.
|
||||
|
||||
* Animates in the element over 300 milliseconds. During the animation, the element assumes its default width and opacity.
|
||||
|
||||
* If there are multiple matching elements, staggers in each element starting at the top of the page, with a 50-millisecond delay between each element.
|
||||
|
||||
## Animation sequence summary
|
||||
|
||||
Angular functions for animating multiple elements start with `query()` to find inner elements, for example gathering all images within a `<div>`. The remaining functions, `stagger()`, `group()`, and `sequence()`, apply cascades or allow you to control how multiple animation steps are applied.
|
||||
|
||||
## More on Angular animations
|
||||
|
||||
You may also be interested in the following:
|
||||
|
||||
* [Introduction to Angular animations](guide/animations)
|
||||
* [Transition and triggers](guide/transition-and-triggers)
|
||||
* [Reusable animations](guide/reusable-animations)
|
||||
* [Route transition animations](guide/route-animations)
|
|
@ -0,0 +1,40 @@
|
|||
# Reusable animations
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
A basic understanding of the following concepts:
|
||||
|
||||
* [Introduction to Angular animations](guide/animations)
|
||||
* [Transition and triggers](guide/transition-and-triggers)
|
||||
|
||||
<hr>
|
||||
|
||||
The [AnimationOptions](https://angular.io/api/animations/AnimationOptions) interface in Angular animations enables you to create animations that you can reuse across different components.
|
||||
|
||||
## Creating reusable animations
|
||||
|
||||
To create a reusable animation, use the [`animation()`](https://angular.io/api/animations/animation) method to define an animation in a separate `.ts` file and declare this animation definition as a `const` export variable. You can then import and reuse this animation in any of your app components using the [`useAnimation()`](https://angular.io/api/animations/useAnimation) API.
|
||||
|
||||
<code-example path="animations/src/app/animations.ts" title="src/app/animations.ts" region="reusable" language="typescript" linenums="false">
|
||||
</code-example>
|
||||
|
||||
In the above code snippet, `transAnimation` is made reusable by declaring it as an export variable.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**Note:** The `height`, `opacity`, `backgroundColor`, and `time` inputs are replaced during runtime.
|
||||
</div>
|
||||
|
||||
You can import the reusable `transAnimation` variable in your component class and reuse it using the `useAnimation()` method as shown below.
|
||||
|
||||
<code-example path="animations/src/app/open-close.component.3.ts" title="src/app/open-close.component.ts" region="reusable" language="typescript" linenums="false">
|
||||
</code-example>
|
||||
|
||||
## More on Angular animations
|
||||
|
||||
You may also be interested in the following:
|
||||
|
||||
* [Introduction to Angular animations](guide/animations)
|
||||
* [Transition and triggers](guide/transition-and-triggers)
|
||||
* [Complex animation Sequences](guide/complex-animation-sequences)
|
||||
* [Route transition animations](guide/route-animations)
|
|
@ -0,0 +1,139 @@
|
|||
# Route transition animations
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
A basic understanding of the following concepts:
|
||||
|
||||
* [Introduction to Angular animations](guide/animations)
|
||||
* [Transition and triggers](guide/transition-and-triggers)
|
||||
* [Reusable animations](guide/reusable-animations)
|
||||
|
||||
<hr>
|
||||
|
||||
Routing enables users to navigate between different routes in an application. When a user navigates from one route to another, the Angular router maps the URL path to a relevant component and displays its view. Animating this route transition can greatly enhance the user experience.
|
||||
|
||||
The Angular router comes with high-level animation functions that let you animate the transitions between views when a route changes. To produce an animation sequence when switching between routes, you need to define nested animation sequences. Start with the top-level component that hosts the view, and nest additional animations in the components that host the embedded views.
|
||||
|
||||
To enable routing transition animation, do the following:
|
||||
|
||||
1. Import the routing module into the application and create a routing configuration that defines the possible routes.
|
||||
2. Add a router outlet to tell the Angular router where to place the activated components in the DOM.
|
||||
3. Define the animation.
|
||||
|
||||
|
||||
Let's illustrate a router transition animation by navigating between two routes, *Home* and *About* associated with the `HomeComponent` and `AboutComponent` views respectively. Both of these component views are children of the top-most view, hosted by `AppComponent`. We'll implement a router transition animation that slides in the new view to the right and slides out the old view when the user navigates between the two routes.
|
||||
|
||||
</br>
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/animations/route-animation.gif" alt="Animations in action" width="440">
|
||||
</figure>
|
||||
|
||||
## Route configuration
|
||||
|
||||
To begin, configure a set of routes using methods available in the `RouterModule` class. This route configuration tells the router how to navigate.
|
||||
|
||||
Use the `RouterModule.forRoot` method to define a set of routes. Also, import this `RouterModule` to the `imports` array of the main module, `AppModule`.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**Note:** Use the `RouterModule.forRoot` method in the root module, `AppModule`, to register top-level application routes and providers. For feature modules, call the `RouterModule.forChild` method to register additional routes.
|
||||
|
||||
</div>
|
||||
|
||||
The following configuration defines the possible routes for the application.
|
||||
|
||||
<code-example path="animations/src/app/app.module.ts" linenums="false" title="src/app/app.module.ts" region="route-animation-data" language="typescript">
|
||||
</code-example>
|
||||
|
||||
The `home` and `about` paths are associated with the `HomeComponent` and `AboutComponent` views. The route configuration tells the Angular router to instantiate the `HomeComponent` and `AboutComponent` views when the navigation matches the corresponding path.
|
||||
|
||||
In addition to `path` and `component`, the `data` property of each route defines the key animation-specific configuration associated with a route. The `data` property value is passed into `AppComponent` when the route changes. You can also pass additional data in route config that is consumed within the animation. The data property value has to match the transitions defined in the `routeAnimation` trigger, which we'll define later.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**Note:** The `data` property names that you use can be arbitrary. For example, the name *animation* used in the example above is an arbitrary choice.
|
||||
|
||||
</div>
|
||||
|
||||
## Router outlet
|
||||
|
||||
After configuring the routes, tell the Angular router where to render the views when matched with a route. You can set a router outlet by inserting a `<router-outlet>` container inside the root `AppComponent` template.
|
||||
|
||||
The `<router-outlet>` container has an attribute directive that contains data about active routes and their states, based on the `data` property that we set in the route configuration.
|
||||
|
||||
<code-example path="animations/src/app/app.component.html" title="src/app/app.component.html" region="route-animations-outlet">
|
||||
</code-example>
|
||||
|
||||
`AppComponent` defines a method that can detect when a view changes. The method assigns an animation state value to the animation trigger (`@routeAnimation`) based on the route configuration `data` property value. Here's an example of an `AppComponent` method that detects when a route change happens.
|
||||
|
||||
<code-example path="animations/src/app/app.component.ts" linenums="false" title="src/app/app.component.ts" region="prepare-router-outlet" language="typescript">
|
||||
</code-example>
|
||||
|
||||
Here, the `prepareRoute()` method takes the value of the output directive (established through `#outlet="outlet"`) and returns a string value representing the state of the animation based on the custom data of the current active route. You can use this data to control which transition to execute for each route.
|
||||
|
||||
## Animation definition
|
||||
|
||||
Animations can be defined directly inside your components. For this example we are defining the animations in a separate file, which allows us to re-use the animations.
|
||||
|
||||
The following code snippet defines a reusable animation named `slideInAnimation`.
|
||||
|
||||
|
||||
<code-example path="animations/src/app/animations.ts" linenums="false" title="src/app/animations.ts" region="route-animations" language="typescript">
|
||||
</code-example>
|
||||
|
||||
The animation definition does several things:
|
||||
|
||||
* Defines two transitions. A single trigger can define multiple states and transitions.
|
||||
* Adjusts the styles of the host and child views to control their relative positions during the transition.
|
||||
* Uses `query()` to determine which child view is entering and which is leaving the host view.
|
||||
|
||||
A route change activates the animation trigger, and a transition matching the state change is applied.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**Note:** The transition states must match the `data` property value defined in the route configuration.
|
||||
</div>
|
||||
|
||||
Make the animation definition available in your application by adding the reusable animation (`slideInAnimation`) to the `animations` metadata of the `AppComponent`.
|
||||
|
||||
<code-example path="animations/src/app/app.component.ts" linenums="false" title="src/app/app.component.ts" region="define" language="typescript">
|
||||
</code-example>
|
||||
|
||||
### Styling the host and child components
|
||||
|
||||
During a transition, a new view is inserted directly after the old one and both elements appear on screen at the same time. To prevent this, apply additional styling to the host view, and to the removed and inserted child views. The host view must use relative positioning, and the child views must use absolute positioning. Adding styling to the views animates the containers in place, without the DOM moving things around.
|
||||
|
||||
<code-example path="animations/src/app/animations.ts" linenums="false" title="src/app/animations.ts" region="style-view" language="typescript">
|
||||
</code-example>
|
||||
|
||||
### Querying the view containers
|
||||
|
||||
Use the `query()` method to find and animate elements within the current host component. The `query(":enter")` statement returns the view that is being inserted, and `query(":leave")` returns the view that is being removed.
|
||||
|
||||
Let's assume that we are routing from the *Home => About*.
|
||||
|
||||
<code-example path="animations/src/app/animations.ts" linenums="false" title="src/app/animations.ts" region="query" language="typescript" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The animation code does the following after styling the views:
|
||||
|
||||
* `query(':enter style({ left: '-100%'})` matches the view that is added and hides the newly added view by positioning it to the far left.
|
||||
* Calls `animateChild()` on the view that is leaving, to run its child animations.
|
||||
* Uses `group()` function to make the inner animations run in parallel.
|
||||
* Within the `group()` function:
|
||||
* Queries the view that is removed and animates it to slide far to the right.
|
||||
* Slides in the new view by animating the view with an easing function and duration. </br>
|
||||
This animation results in the `about` view sliding from the left to right.
|
||||
* Calls the `animateChild()` method on the new view to run its child animations after the main animation completes.
|
||||
|
||||
You now have a basic routable animation that animates routing from one view to another.
|
||||
|
||||
## More on Angular animations
|
||||
|
||||
You may also be interested in the following:
|
||||
|
||||
* [Introduction to Angular animations](guide/animations)
|
||||
* [Transition and triggers](guide/transition-and-triggers)
|
||||
* [Complex animation sequences](guide/complex-animation-sequences)
|
||||
* [Reusable animations](guide/reusable-animations)
|
|
@ -0,0 +1,308 @@
|
|||
# Animations transitions and triggers
|
||||
|
||||
You learned the basics of Angular animations in the [introduction](guide/animations) page.
|
||||
|
||||
In this guide, we go into greater depth on special transition states such as `*` (wildcard) and `void`, and show how these special states are used for elements entering and leaving a view. The chapter also explores on multiple animation triggers, animation callbacks and sequence-based animation using keyframes.
|
||||
|
||||
## Predefined states and wildcard matching
|
||||
|
||||
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.
|
||||
|
||||
For example, a transition of `open => *` applies when the element's state changes from open to anything else.
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/animations/wildcard-state-500.png" alt="wildcard state expressions">
|
||||
</figure>
|
||||
|
||||
Here's another code sample using the wildcard state together with our previous example using the `open` and `closed` states. Instead of defining each state-to-state transition pair, we're now saying that any transition to `closed` takes 1 second, and any transition to `open` takes 0.5 seconds.
|
||||
|
||||
This allows us to add new states without having to include separate transitions for each one.
|
||||
|
||||
<code-example title="src/app/open-close.component.ts" path="animations/src/app/open-close.component.ts" region="trigger-wildcard1" language="typescript" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Use a double arrow syntax to specify state-to-state transitions in both directions.
|
||||
|
||||
<code-example title="src/app/open-close.component.ts" path="animations/src/app/open-close.component.ts" region="trigger-wildcard2" language="typescript" linenums="false">
|
||||
</code-example>
|
||||
|
||||
### Using wildcard state with multiple transition states
|
||||
|
||||
In our two-state button example, the wildcard isn't that useful because there are only two possible states, `open` and `closed`. Wildcard states are better when an element in one particular state has multiple potential states that it can change to. If our button can change from `open` to either `closed` or something like `inProgress`, using a wildcard state could reduce the amount of coding needed.
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/animations/wildcard-3-states.png" alt="wildcard state with 3 states">
|
||||
</figure>
|
||||
|
||||
|
||||
<code-example path="animations/src/app/open-close.component.ts" title="src/app/open-close.component.ts" linenums="false"
|
||||
region="trigger-transition" language="typescript">
|
||||
</code-example>
|
||||
|
||||
|
||||
The `* => *` transition applies when any change between two states takes place.
|
||||
|
||||
Transitions are matched in the order in which they are defined. Thus, you can apply other transitions on top of the `* => *` (any-to-any) transition. For example, define style changes or animations that would apply just to `open => closed`, or just to `closed => open`, and then use `* => *` as a fallback for state pairings that aren't otherwise called out.
|
||||
|
||||
To do this, list the more specific transitions *before* `* => *`.
|
||||
|
||||
### Using wildcards with styles
|
||||
|
||||
Use the wildcard `*` with a style to tell the animation to use whatever the current style value is, and animate with that. Wildcard is a fallback value that's used if the state being animated isn't declared within the trigger.
|
||||
|
||||
<code-example path="animations/src/app/open-close.component.ts" title="src/app/open-close.component.ts" linenums="false"
|
||||
region="transition4" language="typescript">
|
||||
</code-example>
|
||||
|
||||
### Void state
|
||||
|
||||
You can use the `void` state to configure transitions for an element that is entering or leaving a page. See [Animating entering and leaving a view](#enter-leave-view).
|
||||
|
||||
|
||||
### Combining wildcard and void states
|
||||
|
||||
You can combine wildcard and void states in a transition to trigger animations that enter and leave the page:
|
||||
|
||||
* A transition of `* => void` applies when the element leaves a view, regardless of what state it was in before it left.
|
||||
|
||||
* A transition of `void => *` applies when the element enters a view, regardless of what state it assumes when entering.
|
||||
|
||||
* The wildcard state `*` matches to *any* state, including `void`.
|
||||
|
||||
## Animating entering and leaving a view
|
||||
|
||||
This section shows how to animate elements entering or leaving a page.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**Note:** For our purposes, an element entering or leaving a view is equivalent to being inserted or removed from the DOM.
|
||||
|
||||
</div>
|
||||
|
||||
Now we'll add a new behavior:
|
||||
|
||||
* When you add a hero to the list of heroes, it appears to fly onto the page from the left.
|
||||
* When you remove a hero from the list, it appears to fly out to the right.
|
||||
|
||||
<code-example path="animations/src/app/hero-list-enter-leave.component.ts" title="src/app/hero-list-enter-leave.component.ts" region="animationdef" language="typescript" linenums="false">
|
||||
</code-example>
|
||||
|
||||
In the above code, we applied the `void` state when the HTML element isn't attached to a view.
|
||||
|
||||
|
||||
{@a enter-leave-view}
|
||||
|
||||
## :enter and :leave aliases
|
||||
|
||||
`:enter` and `:leave` are aliases for the `void => *` and `* => void` transitions. These aliases are used by several animation functions.
|
||||
|
||||
<code-example hideCopy language="typescript">
|
||||
transition ( ':enter', [ ... ] ); // alias for void => *
|
||||
transition ( ':leave', [ ... ] ); // alias for * => void
|
||||
</code-example>
|
||||
|
||||
It's harder to target an element that is entering a view because it isn't in the DOM yet.
|
||||
So, use the aliases `:enter` and `:leave` to target HTML elements that are inserted or removed from a view.
|
||||
|
||||
### Use of \*ngIf and \*ngFor with :enter and :leave
|
||||
|
||||
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.
|
||||
|
||||
In this example, we have 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" title="src/app/insert-remove.component.html" region="insert-remove" language="typescript">
|
||||
</code-example>
|
||||
|
||||
In the component file, the `:enter` transition sets an initial opacity of 0, and then animates it to change that opacity to 1 as the element is inserted into the view.
|
||||
|
||||
<code-example path="animations/src/app/insert-remove.component.ts" title="src/app/insert-remove.component.ts" region="enter-leave-trigger" language="typescript">
|
||||
</code-example>
|
||||
|
||||
Note that this example doesn't need to use `state()`.
|
||||
|
||||
## :increment and :decrement in transitions
|
||||
|
||||
The `transition()` function takes additional selector values, `:increment` and `:decrement`. Use these to kick off a transition when a numeric value has increased or decreased in value.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**Note:** The following example uses `query()` and `stagger()` methods, which is discussed in the [complex sequences](guide/complex-animation-sequences#complex-sequence) page.
|
||||
|
||||
</div>
|
||||
|
||||
<code-example path="animations/src/app/hero-list-page.component.ts" title="src/app/hero-list-page.component.ts" region="increment" language="typescript" linenums="false"></code-example>
|
||||
|
||||
## Boolean values in transitions
|
||||
|
||||
If a trigger contains a boolean value as a binding value, then this value can be matched using a `transition()` expression that compares `true` and `false`, or `1` and `0`.
|
||||
|
||||
<code-example path="animations/src/app/open-close.component.2.html" title="src/app/open-close.component.html" region="trigger-boolean">
|
||||
</code-example>
|
||||
|
||||
In the code snippet above, the HTML template binds a `<div>` element to a trigger named `openClose` with a status expression of `isOpen`, and with possible values of `true` and `false`. This is an alternative to the practice of creating two named states of `open` and `close`.
|
||||
|
||||
In the component code, in the `@Component` metadata under the `animations:` property, when the state evaluates to `true` (meaning "open" here), the associated HTML element's height is a wildcard style or default. In this case, use whatever height the element already had before the animation started. When the element is "closed," the element animates to a height of 0, which makes it invisible.
|
||||
|
||||
<code-example path="animations/src/app/open-close.component.2.ts" title="src/app/open-close.component.ts" region="trigger-boolean" language="typescript">
|
||||
</code-example>
|
||||
|
||||
## Multiple animation triggers
|
||||
|
||||
You can define more than one animation trigger for a component. You can attach animation triggers to different elements, and the parent-child relationships among the elements affect how and when the animations run.
|
||||
|
||||
### Parent-child animations
|
||||
|
||||
Each time an animation is triggered in Angular, the parent animation always get priority and child animations are blocked. In order for a child animation to run, the parent animation must query each of the elements containing child animations and then allow the animations to run using the [`animateChild()`](https://angular.io/api/animations/animateChild) function.
|
||||
|
||||
#### Disabling an animation on an HTML element
|
||||
|
||||
A special animation control binding called `@.disabled` can be placed on an HTML element to disable animations on that element, as well as any nested elements. When true, the `@.disabled` binding prevents all animations from rendering.
|
||||
|
||||
The code sample below shows how to use this feature.
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane path="animations/src/app/open-close.component.4.html" title="src/app/open-close.component.html" region="toggle-animation">
|
||||
</code-pane>
|
||||
|
||||
<code-pane path="animations/src/app/open-close.component.4.ts" title="src/app/open-close.component.ts" region="toggle-animation" language="typescript">
|
||||
</code-pane>
|
||||
|
||||
</code-tabs>
|
||||
|
||||
When the `@.disabled` binding is true, the `@childAnimation` trigger doesn't kick off.
|
||||
|
||||
When an element within an HTML template has animations disabled using the `@.disabled` host binding, animations are disabled on all inner elements as well. You can't selectively disable multiple animations on a single element.
|
||||
|
||||
However, selective child animations can still be run on a disabled parent in one of the following ways:
|
||||
|
||||
* A parent animation can use the [`query()`](https://angular.io/api/animations/query) function to collect inner elements located in disabled areas of the HTML template. Those elements can still animate.
|
||||
|
||||
* A subanimation can be queried by a parent and then later animated with the `animateChild()` function.
|
||||
|
||||
#### Disabling all animations
|
||||
|
||||
To disable all animations for an Angular app, place the `@.disabled` host binding on the topmost Angular component.
|
||||
|
||||
<code-example path="animations/src/app/app.component.ts" title="src/app/app.component.ts" region="toggle-app-animations" language="typescript" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**Note:** Disabling animations application-wide is useful during end-to-end (E2E) testing.
|
||||
</div>
|
||||
|
||||
## Animation callbacks
|
||||
|
||||
The animation `trigger()` function emits *callbacks* when it starts and when it finishes. In the example below we have a component that contains an `openClose` trigger.
|
||||
|
||||
<code-example path="animations/src/app/open-close.component.ts" title="src/app/open-close.component.ts" region="events1" language="typescript" linenums="false">
|
||||
</code-example>
|
||||
|
||||
In the HTML template, the animation event is passed back via `$event`, as `@trigger.start` and `@trigger.done`, where `trigger` is the name of the trigger being used. In our example, the trigger `openClose` appears as follows.
|
||||
|
||||
<code-example path="animations/src/app/open-close.component.3.html" title="src/app/open-close.component.html" region="callbacks">
|
||||
</code-example>
|
||||
|
||||
A potential use for animation callbacks could be to cover for a slow API call, such as a database lookup. For example, you could set up the **InProgress** button to have its own looping animation where it pulsates or does some other visual motion while the backend system operation finishes.
|
||||
|
||||
Then, another animation can be called when the current animation finishes. For example, the button goes from the `inProgress` state to the `closed` state when the API call is completed.
|
||||
|
||||
An animation can influence an end user to *perceive* the operation as faster, even when it isn't. Thus, a simple animation can be a cost-effective way to keep users happy, rather than seeking to improve the speed of a server call and having to compensate for circumstances beyond your control, such as an unreliable network connection.
|
||||
|
||||
Callbacks can serve as a debugging tool, for example in conjunction with `console.warn()` to view the application's progress in a browser's Developer JavaScript Console. The following code snippet creates console log output for our original example, a button with the two states of `open` and `closed`.
|
||||
|
||||
<code-example path="animations/src/app/open-close.component.ts" title="src/app/open-close.component.ts" region="events" language="typescript" linenums="false">
|
||||
</code-example>
|
||||
|
||||
{@a keyframes}
|
||||
|
||||
## Keyframes
|
||||
|
||||
In the previous section, we saw a simple two-state transition. Now we'll create an animation with multiple steps run in sequence using *keyframes*.
|
||||
|
||||
Angular's `keyframe()` function is similar to keyframes in CSS. Keyframes allow several style changes within a single timing segment. For example, our button, instead of fading, could change color several times over a single 2-second timespan.
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/animations/keyframes-500.png" alt="keyframes">
|
||||
</figure>
|
||||
|
||||
The code for this color change might look like this.
|
||||
|
||||
<code-example path="animations/src/app/status-slider.component.ts" title="src/app/status-slider.component.ts" region="keyframes" language="typescript" linenums="false">
|
||||
</code-example>
|
||||
|
||||
### Offset
|
||||
|
||||
Keyframes include an *offset* that defines the point in the animation where each style change occurs. Offsets are relative measures from zero to one, marking the beginning and end of the animation, respectively.
|
||||
|
||||
Defining offsets for keyframes is optional. If you omit them, evenly spaced offsets are automatically assigned. For example, three keyframes without predefined offsets receive offsets of 0, 0.5, and 1. Specifying an offset of 0.8 for the middle transition in the above example might look like this.
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/animations/keyframes-offset-500.png" alt="keyframes with offset">
|
||||
</figure>
|
||||
|
||||
The code with offsets specified would be as follows.
|
||||
|
||||
<code-example path="animations/src/app/status-slider.component.ts" title="src/app/status-slider.component.ts" region="keyframesWithOffsets" language="typescript">
|
||||
</code-example>
|
||||
|
||||
You can combine keyframes with `duration`, `delay`, and `easing` within a single animation.
|
||||
|
||||
### Keyframes with a pulsation
|
||||
|
||||
Use ketframes to create a pulse effect in your animations by defining styles at specific offset throughout the animation.
|
||||
|
||||
Here's an example of using keyframes to create a pulse effect:
|
||||
|
||||
* The original `open` and `closed` states, with the original changes in height, color, and opacity, occurring over a timeframe of 1 second
|
||||
|
||||
* A keyframes sequence inserted in the middle that causes the button to appear to pulsate irregularly over the course of that same 1-second timeframe
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/animations/keyframes-pulsation.png" alt="keyframes with irregular pulsation">
|
||||
</figure>
|
||||
|
||||
The code snippet for this animation might look like this.
|
||||
|
||||
<code-example path="animations/src/app/open-close.component.1.ts" title="src/app/open-close.component.ts" region="trigger" language="typescript" linenums="false">
|
||||
</code-example>
|
||||
|
||||
### Animatable properties and units
|
||||
|
||||
Angular's animation support builds on top of web animations, so 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](https://www.w3.org/TR/css-transitions-1/) page.
|
||||
|
||||
For positional properties with a numeric value, define a unit by providing the value as a string, in quotes, with the appropriate suffix:
|
||||
|
||||
* 50 pixels: `'50px'`
|
||||
* Relative font size: `'3em'`
|
||||
* Percentage: `'100%'`
|
||||
|
||||
If you don't provide a unit when specifying dimension, Angular assumes a default unit of pixels, or px. Expressing 50 pixels as `50` is the same as saying `'50px'`.
|
||||
|
||||
### Automatic property calculation with wildcards
|
||||
|
||||
Sometimes you don't know the value of a dimensional style property until runtime. For example, elements often have widths and heights that depend on their content and the screen size. These properties are often challenging to animate using CSS.
|
||||
|
||||
In these cases, you can use a special wildcard `*` property value under `style()`, so that the value of that particular style property is computed at runtime and then plugged into the animation.
|
||||
|
||||
In this example, we have a trigger called `shrinkOut`, used when an HTML element leaves the page. The animation takes whatever height the element has before it leaves, and animates from that height to zero.
|
||||
|
||||
<code-example path="animations/src/app/hero-list-auto.component.ts" title="src/app/hero-list-auto.component.ts" region="auto-calc" language="typescript" linenums="false"></code-example>
|
||||
|
||||
### Keyframes summary
|
||||
|
||||
The `keyframes()` function in Angular allows you to specify multiple interim styles within a single transition, with an optional offset to define the point in the animation where each style change occurs.
|
||||
|
||||
## More on Angular animations
|
||||
|
||||
You may also be interested in the following:
|
||||
|
||||
* [Introduction to Angular animations](guide/animations)
|
||||
* [Complex animation sequences](guide/complex-animation-sequences)
|
||||
* [Reusable animations](guide/reusable-animations)
|
||||
* [Route transition animations](guide/route-animations)
|
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 193 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 55 KiB |
After Width: | Height: | Size: 47 KiB |
|
@ -202,14 +202,10 @@
|
|||
"url": "guide/pipes",
|
||||
"title": "Pipes",
|
||||
"tooltip": "Pipes transform displayed values within a template."
|
||||
},
|
||||
{
|
||||
"url": "guide/animations",
|
||||
"title": "Animations",
|
||||
"tooltip": "A guide to Angular's animation system."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"title": "Forms",
|
||||
"tooltip": "Angular Forms",
|
||||
|
@ -387,6 +383,38 @@
|
|||
"title": "Routing & Navigation",
|
||||
"tooltip": "Discover the basics of screen navigation with the Angular Router."
|
||||
},
|
||||
{
|
||||
"title": "Animations",
|
||||
"tooltip": "A guide to Angular's animation system.",
|
||||
"children": [
|
||||
{
|
||||
"url": "guide/animations",
|
||||
"title": "Introduction",
|
||||
"tooltip": "Basic techniques in Angular animations."
|
||||
},
|
||||
{
|
||||
"url": "guide/transition-and-triggers",
|
||||
"title": "Transition and Triggers",
|
||||
"tooltip": "Advanced techniques in transition and triggers."
|
||||
},
|
||||
{
|
||||
"url": "guide/complex-animation-sequences",
|
||||
"title": "Complex Sequences",
|
||||
"tooltip": "Complex Angular animation sequences."
|
||||
},
|
||||
{
|
||||
"url": "guide/reusable-animations",
|
||||
"title": "Reusable Animations",
|
||||
"tooltip": "Creating reusable animations."
|
||||
},
|
||||
{
|
||||
"url": "guide/route-animations",
|
||||
"title": "Route Transition Animations",
|
||||
"tooltip": "Animate route transitions."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"url": "guide/testing",
|
||||
"title": "Testing",
|
||||
|
|