feat(animation): add animation utility (#18918)

* Add new keyframes proof of concept

* update esm import

* add base before and after methods, add tests

* add base before and after hooks

* update clean up methods, add tests

* add web animations support, change to arrow functions

* remove console logs

* add from, to, fromTo, and other properties

* add more tests, fix onFinish functionality, being testing with nav transitions

* add progress methods, use force linear

* run linter

* Add playSync

* integrate animations with framework components

* onFinish now supports multiple callbacks

* change const to let

* testing reverse

* add support for both animation utilities

* bug fix

* export createAnimation, a few tweaks

* add base tests

* fix issue with onFinish being called out of order. added tests

* fix race conditions in tests

* clean up

* fix bug where onFinish not calling for empty elements array,  update test

* clean up

* fix treeshaking, remove old comments

* remove old tests

* Add test for animationbuilder backwards compat

* update typings for menu controller

* mock web animations in tests

* run build

* fix type errors

* sync with master

* use requestAnimationFrame instead of writeTask

* fix flaky tests, fix menu

* fix ordering

* update webdriver

* fix wrong version

* Revert "fix wrong version"

This reverts commit be91296e9701399f8d784b08d09a3c475ca15df7.

Revert chromedriver update

* Revert "update webdriver"

This reverts commit e49bc9d76e335a0af5828725065399bd6795fa37.

Revert chromedriver update

* expose raw animation object, add tests

* add stylesheet recycling

* finalize before and after hook tests

* a few styling changes

* fix lint warnings

* get rid of old code

* Fix progressStep overflow bug

* disable reuse stylesheet

* small updates

* fix old animation create

* setStyleProperty helper

* reuse keyframe styles

* keyframes

* fix css animation issue with display: none, add tests

* add comment

* fix issue with progress animations and css animations

* clean up

* clean up pt2

* fix tests

* fix linter

* add fill for overlays

* fix swipe to go back

* clean up css animations when done

* fix edge cases with css animations

* fix menu open and close

* add reset function

* clean up reset fn

* Fix issue where animation always being reset

* allow updating animations on the fly

* add clear onfinish method

* fix linter

* add callback options, expand force direction

* ensure opts is defined

* fix css animations open and close for menus

* remove test

* add extra check

* clean up

* fix css anim bug swipe to go back

* fix pause

* setup alt animation to avoid flickering

* clean up

* reset flags on destroy

* add ability to change duration on progressEnd

* fix flicker on duration change for css animations

* fix ios transition

* remove unneeded recursion

* increase durability of updating css animations on the fly

* fix gesture anim

* fix web anim as well. more work for cleanup

* simplify progressEnd for css animations

* fix swipe to go back race condition

* clean up

* Add todo

* fix one more bug
This commit is contained in:
Liam DeBeasi
2019-08-12 10:05:04 -04:00
committed by GitHub
parent e33cf854a9
commit 30ca46ab12
70 changed files with 3974 additions and 762 deletions

View File

@ -1,78 +1,78 @@
import { Animation } from '../../interface';
import { IonicAnimation } from '../../interface';
import { createAnimation } from '../animation/animation';
import { TransitionOptions } from '../transition';
export const shadow = <T extends Element>(el: T): ShadowRoot | T => {
return el.shadowRoot || el;
};
export const iosTransitionAnimation = (AnimationC: Animation, navEl: HTMLElement, opts: TransitionOptions): Promise<Animation> => {
export const iosTransitionAnimation = (navEl: HTMLElement, opts: TransitionOptions): IonicAnimation => {
try {
const DURATION = 540;
const EASING = 'cubic-bezier(0.32,0.72,0,1)';
const OPACITY = 'opacity';
const TRANSFORM = 'transform';
const CENTER = '0%';
const OFF_OPACITY = 0.8;
const DURATION = 540;
const EASING = 'cubic-bezier(0.32,0.72,0,1)';
const OPACITY = 'opacity';
const TRANSFORM = 'transform';
const TRANSLATEX = 'translateX';
const CENTER = '0%';
const OFF_OPACITY = 0.8;
const isRTL = (navEl.ownerDocument as any).dir === 'rtl';
const OFF_RIGHT = isRTL ? '-99.5%' : '99.5%';
const OFF_LEFT = isRTL ? '33%' : '-33%';
const backDirection = (opts.direction === 'back');
const isRTL = (navEl.ownerDocument as any).dir === 'rtl';
const OFF_RIGHT = isRTL ? '-99.5%' : '99.5%';
const OFF_LEFT = isRTL ? '33%' : '-33%';
const enteringEl = opts.enteringEl;
const leavingEl = opts.leavingEl;
const enteringEl = opts.enteringEl;
const leavingEl = opts.leavingEl;
const contentEl = enteringEl.querySelector(':scope > ion-content');
const headerEls = enteringEl.querySelectorAll(':scope > ion-header > *:not(ion-toolbar), :scope > ion-footer > *');
const enteringToolBarEls = enteringEl.querySelectorAll(':scope > ion-header > ion-toolbar');
const enteringContent = new AnimationC();
const backDirection = (opts.direction === 'back');
const contentEl = enteringEl.querySelector(':scope > ion-content');
const headerEls = enteringEl.querySelectorAll(':scope > ion-header > *:not(ion-toolbar), :scope > ion-footer > *');
const enteringToolBarEls = enteringEl.querySelectorAll(':scope > ion-header > ion-toolbar');
const rootTransition = new AnimationC();
rootTransition
.addElement(enteringEl)
.duration(opts.duration || DURATION)
.easing(opts.easing || EASING)
.beforeRemoveClass('ion-page-invisible');
const rootAnimation = createAnimation();
const enteringContentAnimation = createAnimation();
if (leavingEl && navEl) {
const navDecor = new AnimationC();
navDecor
.addElement(navEl);
rootAnimation
.addElement(enteringEl)
.duration(opts.duration || DURATION)
.easing(opts.easing || EASING)
.fill('both')
.beforeRemoveClass('ion-page-invisible');
rootTransition.add(navDecor);
}
if (leavingEl && navEl) {
const navDecorAnimation = createAnimation();
navDecorAnimation.addElement(navEl);
rootAnimation.addAnimation(navDecorAnimation);
}
if (!contentEl && enteringToolBarEls.length === 0 && headerEls.length === 0) {
enteringContent.addElement(enteringEl.querySelector(':scope > .ion-page, :scope > ion-nav, :scope > ion-tabs'));
} else {
enteringContent
.addElement(contentEl)
.addElement(headerEls);
}
if (!contentEl && enteringToolBarEls.length === 0 && headerEls.length === 0) {
enteringContentAnimation.addElement(enteringEl.querySelector(':scope > .ion-page, :scope > ion-nav, :scope > ion-tabs'));
} else {
enteringContentAnimation.addElement(contentEl);
enteringContentAnimation.addElement(headerEls);
}
rootTransition.add(enteringContent);
rootAnimation.addAnimation(enteringContentAnimation);
if (backDirection) {
enteringContent
.beforeClearStyles([OPACITY])
.fromTo(TRANSLATEX, OFF_LEFT, CENTER, true)
.fromTo(OPACITY, OFF_OPACITY, 1, true);
} else {
// entering content, forward direction
enteringContent
.beforeClearStyles([OPACITY])
.fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true);
if (backDirection) {
enteringContentAnimation
.beforeClearStyles([OPACITY])
.fromTo('transform', `translateX(${OFF_LEFT})`, `translateX(${CENTER})`)
.fromTo(OPACITY, OFF_OPACITY, 1);
} else {
// entering content, forward direction
enteringContentAnimation
.beforeClearStyles([OPACITY])
.fromTo('transform', `translateX(${OFF_RIGHT})`, `translateX(${CENTER})`);
}
if (contentEl) {
const enteringTransitionEffectEl = shadow(contentEl).querySelector('.transition-effect');
if (enteringTransitionEffectEl) {
const enteringTransitionCoverEl = enteringTransitionEffectEl.querySelector('.transition-cover');
const enteringTransitionShadowEl = enteringTransitionEffectEl.querySelector('.transition-shadow');
const enteringTransitionEffect = new AnimationC();
const enteringTransitionCover = new AnimationC();
const enteringTransitionShadow = new AnimationC();
const enteringTransitionEffect = createAnimation();
const enteringTransitionCover = createAnimation();
const enteringTransitionShadow = createAnimation();
enteringTransitionEffect
.addElement(enteringTransitionEffectEl)
@ -82,113 +82,111 @@ export const iosTransitionAnimation = (AnimationC: Animation, navEl: HTMLElement
enteringTransitionCover
.addElement(enteringTransitionCoverEl)
.beforeClearStyles([OPACITY])
.fromTo(OPACITY, 0, 0.1, true);
.fromTo(OPACITY, 0, 0.1);
enteringTransitionShadow
.addElement(enteringTransitionShadowEl)
.beforeClearStyles([OPACITY])
.fromTo(OPACITY, 0.70, 0.03, true);
.fromTo(OPACITY, 0.03, 0.70);
enteringContent
.add(enteringTransitionEffect)
.add(enteringTransitionCover)
.add(enteringTransitionShadow);
enteringTransitionEffect.addAnimation([enteringTransitionCover, enteringTransitionShadow]);
enteringContentAnimation.addAnimation([enteringTransitionEffect]);
}
}
}
enteringToolBarEls.forEach(enteringToolBarEl => {
const enteringToolBar = new AnimationC();
const enteringTitle = new AnimationC();
const enteringToolBarButtons = new AnimationC();
const enteringToolBarItems = new AnimationC();
const enteringToolBarBg = new AnimationC();
const enteringBackButton = new AnimationC();
const backButtonEl = enteringToolBarEl.querySelector('ion-back-button');
enteringToolBarEls.forEach(enteringToolBarEl => {
const enteringToolBar = createAnimation();
enteringToolBar.addElement(enteringToolBarEl);
rootAnimation.addAnimation(enteringToolBar);
enteringToolBar.addElement(enteringToolBarEl);
rootTransition.add(enteringToolBar);
const enteringTitle = createAnimation();
enteringTitle.addElement(enteringToolBarEl.querySelector('ion-title'));
enteringTitle.addElement(enteringToolBarEl.querySelector('ion-title'));
const enteringToolBarButtons = createAnimation();
enteringToolBarButtons.addElement(enteringToolBarEl.querySelectorAll('ion-buttons,[menuToggle]'));
enteringToolBarButtons.addElement(enteringToolBarEl.querySelectorAll('ion-buttons,[menuToggle]'));
const enteringToolBarItems = createAnimation();
enteringToolBarItems.addElement(enteringToolBarEl.querySelectorAll(':scope > *:not(ion-title):not(ion-buttons):not([menuToggle])'));
enteringToolBarItems.addElement(enteringToolBarEl.querySelectorAll(':scope > *:not(ion-title):not(ion-buttons):not([menuToggle])'));
const enteringToolBarBg = createAnimation();
enteringToolBarBg.addElement(shadow(enteringToolBarEl).querySelector('.toolbar-background'));
enteringToolBarBg.addElement(shadow(enteringToolBarEl).querySelector('.toolbar-background'));
if (backButtonEl) {
enteringBackButton.addElement(backButtonEl);
}
enteringToolBar
.add(enteringTitle)
.add(enteringToolBarButtons)
.add(enteringToolBarItems)
.add(enteringToolBarBg)
.add(enteringBackButton);
enteringTitle.fromTo(OPACITY, 0.01, 1, true);
enteringToolBarButtons.fromTo(OPACITY, 0.01, 1, true);
enteringToolBarItems.fromTo(OPACITY, 0.01, 1, true);
if (backDirection) {
enteringTitle.fromTo(TRANSLATEX, OFF_LEFT, CENTER, true);
enteringToolBarItems.fromTo(TRANSLATEX, OFF_LEFT, CENTER, true);
// back direction, entering page has a back button
enteringBackButton.fromTo(OPACITY, 0.01, 1, true);
} else {
// entering toolbar, forward direction
enteringTitle.fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true);
enteringToolBarItems.fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true);
enteringToolBarBg
.beforeClearStyles([OPACITY])
.fromTo(OPACITY, 0.01, 1, true);
// forward direction, entering page has a back button
enteringBackButton.fromTo(OPACITY, 0.01, 1, true);
const enteringBackButton = createAnimation();
const backButtonEl = enteringToolBarEl.querySelector('ion-back-button');
if (backButtonEl) {
const enteringBackBtnText = new AnimationC();
enteringBackBtnText
.addElement(shadow(backButtonEl).querySelector('.button-text'))
.fromTo(TRANSLATEX, (isRTL ? '-100px' : '100px'), '0px');
enteringToolBar.add(enteringBackBtnText);
enteringBackButton.addElement(backButtonEl);
}
}
});
// setup leaving view
if (leavingEl) {
const leavingContent = new AnimationC();
const leavingContentEl = leavingEl.querySelector(':scope > ion-content');
enteringToolBar.addAnimation([enteringTitle, enteringToolBarButtons, enteringToolBarItems, enteringToolBarBg, enteringBackButton]);
enteringTitle.fromTo(OPACITY, 0.01, 1);
enteringToolBarButtons.fromTo(OPACITY, 0.01, 1);
enteringToolBarItems.fromTo(OPACITY, 0.01, 1);
leavingContent
.addElement(leavingContentEl)
.addElement(leavingEl.querySelectorAll(':scope > ion-header > *:not(ion-toolbar), :scope > ion-footer > *'));
if (backDirection) {
enteringTitle.fromTo('transform', `translateX(${OFF_LEFT})`, `translateX(${CENTER})`);
rootTransition.add(leavingContent);
enteringToolBarItems.fromTo('transform', `translateX(${OFF_LEFT})`, `translateX(${CENTER})`);
if (backDirection) {
// leaving content, back direction
leavingContent
.beforeClearStyles([OPACITY])
.fromTo(TRANSLATEX, CENTER, (isRTL ? '-100%' : '100%'));
// back direction, entering page has a back button
enteringBackButton.fromTo(OPACITY, 0.01, 1);
} else {
// entering toolbar, forward direction
enteringTitle.fromTo('transform', `translateX(${OFF_RIGHT})`, `translateX(${CENTER})`);
enteringToolBarItems.fromTo('transform', `translateX(${OFF_RIGHT})`, `translateX(${CENTER})`);
enteringToolBarBg
.beforeClearStyles([OPACITY])
.fromTo(OPACITY, 0.01, 1);
// forward direction, entering page has a back button
enteringBackButton.fromTo(OPACITY, 0.01, 1);
if (backButtonEl) {
const enteringBackBtnText = createAnimation();
enteringBackBtnText
.addElement(shadow(backButtonEl).querySelector('.button-text'))
.fromTo(`transform`, (isRTL ? 'translateX(-100px)' : 'translateX(100px)'), 'translateX(0px)');
enteringToolBar.addAnimation(enteringBackBtnText);
}
}
});
// setup leaving view
if (leavingEl) {
const leavingContent = createAnimation();
const leavingContentEl = leavingEl.querySelector(':scope > ion-content');
leavingContent.addElement(leavingContentEl);
leavingContent.addElement(leavingEl.querySelectorAll(':scope > ion-header > *:not(ion-toolbar), :scope > ion-footer > *'));
rootAnimation.addAnimation(leavingContent);
if (backDirection) {
// leaving content, back direction
leavingContent
.beforeClearStyles([OPACITY])
.fromTo('transform', `translateX(${CENTER})`, (isRTL ? 'translateX(-100%)' : 'translateX(100%)'));
} else {
// leaving content, forward direction
leavingContent
.fromTo('transform', `translateX(${CENTER})`, `translateX(${OFF_LEFT})`)
.fromTo(OPACITY, 1, OFF_OPACITY);
}
if (leavingContentEl) {
const leavingTransitionEffectEl = shadow(leavingContentEl).querySelector('.transition-effect');
if (leavingTransitionEffectEl) {
const leavingTransitionCoverEl = leavingTransitionEffectEl.querySelector('.transition-cover');
const leavingTransitionShadowEl = leavingTransitionEffectEl.querySelector('.transition-shadow');
const leavingTransitionEffect = new AnimationC();
const leavingTransitionCover = new AnimationC();
const leavingTransitionShadow = new AnimationC();
const leavingTransitionEffect = createAnimation();
const leavingTransitionCover = createAnimation();
const leavingTransitionShadow = createAnimation();
leavingTransitionEffect
.addElement(leavingTransitionEffectEl)
@ -198,104 +196,89 @@ export const iosTransitionAnimation = (AnimationC: Animation, navEl: HTMLElement
leavingTransitionCover
.addElement(leavingTransitionCoverEl)
.beforeClearStyles([OPACITY])
.fromTo(OPACITY, 0.1, 0, true);
.fromTo(OPACITY, 0.1, 0);
leavingTransitionShadow
.addElement(leavingTransitionShadowEl)
.beforeClearStyles([OPACITY])
.fromTo(OPACITY, 0.70, 0.03, true);
.fromTo(OPACITY, 0.70, 0.03);
leavingContent
.add(leavingTransitionEffect)
.add(leavingTransitionCover)
.add(leavingTransitionShadow);
leavingTransitionEffect.addAnimation([leavingTransitionCover, leavingTransitionShadow]);
leavingContent.addAnimation([leavingTransitionEffect]);
}
}
} else {
// leaving content, forward direction
leavingContent
.fromTo(TRANSLATEX, CENTER, OFF_LEFT, true)
.fromTo(OPACITY, 1, OFF_OPACITY, true);
const leavingToolBarEls = leavingEl.querySelectorAll(':scope > ion-header > ion-toolbar');
leavingToolBarEls.forEach(leavingToolBarEl => {
const leavingToolBar = createAnimation();
leavingToolBar.addElement(leavingToolBarEl);
const leavingTitle = createAnimation();
leavingTitle.addElement(leavingToolBarEl.querySelector('ion-title'));
const leavingToolBarButtons = createAnimation();
leavingToolBarButtons.addElement(leavingToolBarEl.querySelectorAll('ion-buttons,[menuToggle]'));
const leavingToolBarItems = createAnimation();
const leavingToolBarItemEls = leavingToolBarEl.querySelectorAll(':scope > *:not(ion-title):not(ion-buttons):not([menuToggle])');
if (leavingToolBarItemEls.length > 0) {
leavingToolBarItems.addElement(leavingToolBarItemEls);
}
const leavingToolBarBg = createAnimation();
leavingToolBarBg.addElement(shadow(leavingToolBarEl).querySelector('.toolbar-background'));
const leavingBackButton = createAnimation();
const backButtonEl = leavingToolBarEl.querySelector('ion-back-button');
if (backButtonEl) {
leavingBackButton.addElement(backButtonEl);
}
leavingToolBar.addAnimation([leavingTitle, leavingToolBarButtons, leavingToolBarItems, leavingBackButton, leavingToolBarBg]);
rootAnimation.addAnimation(leavingToolBar);
// fade out leaving toolbar items
leavingBackButton.fromTo(OPACITY, 0.99, 0);
leavingTitle.fromTo(OPACITY, 0.99, 0);
leavingToolBarButtons.fromTo(OPACITY, 0.99, 0);
leavingToolBarItems.fromTo(OPACITY, 0.99, 0);
if (backDirection) {
// leaving toolbar, back direction
leavingTitle.fromTo('transform', `translateX(${CENTER})`, (isRTL ? 'translateX(-100%)' : 'translateX(100%)'));
leavingToolBarItems.fromTo('transform', `translateX(${CENTER})`, (isRTL ? 'translateX(-100%)' : 'translateX(100%)'));
// leaving toolbar, back direction, and there's no entering toolbar
// should just slide out, no fading out
leavingToolBarBg
.beforeClearStyles([OPACITY])
.fromTo(OPACITY, 1, 0.01);
if (backButtonEl) {
const leavingBackBtnText = createAnimation();
leavingBackBtnText.addElement(shadow(backButtonEl).querySelector('.button-text'));
leavingBackBtnText.fromTo('transform', `translateX(${CENTER})`, `translateX(${(isRTL ? -124 : 124) + 'px'})`);
leavingToolBar.addAnimation(leavingBackBtnText);
}
} else {
// leaving toolbar, forward direction
leavingTitle
.fromTo('transform', `translateX(${CENTER})`, `translateX(${OFF_LEFT})`)
.afterClearStyles([TRANSFORM]);
leavingToolBarItems
.fromTo('transform', `translateX(${CENTER})`, `translateX(${OFF_LEFT})`)
.afterClearStyles([TRANSFORM, OPACITY]);
leavingBackButton.afterClearStyles([OPACITY]);
leavingTitle.afterClearStyles([OPACITY]);
leavingToolBarButtons.afterClearStyles([OPACITY]);
}
});
}
const leavingToolBarEls = leavingEl.querySelectorAll(':scope > ion-header > ion-toolbar');
leavingToolBarEls.forEach(leavingToolBarEl => {
const leavingToolBar = new AnimationC();
const leavingTitle = new AnimationC();
const leavingToolBarButtons = new AnimationC();
const leavingToolBarItems = new AnimationC();
const leavingToolBarItemEls = leavingToolBarEl.querySelectorAll(':scope > *:not(ion-title):not(ion-buttons):not([menuToggle])');
const leavingToolBarBg = new AnimationC();
const leavingBackButton = new AnimationC();
const backButtonEl = leavingToolBarEl.querySelector('ion-back-button');
leavingToolBar.addElement(leavingToolBarEl);
leavingTitle.addElement(leavingToolBarEl.querySelector('ion-title'));
leavingToolBarButtons.addElement(leavingToolBarEl.querySelectorAll('ion-buttons,[menuToggle]'));
if (leavingToolBarItemEls.length > 0) {
leavingToolBarItems.addElement(leavingToolBarItemEls);
}
leavingToolBarBg.addElement(shadow(leavingToolBarEl).querySelector('.toolbar-background'));
if (backButtonEl) {
leavingBackButton.addElement(backButtonEl);
}
leavingToolBar
.add(leavingTitle)
.add(leavingToolBarButtons)
.add(leavingToolBarItems)
.add(leavingBackButton)
.add(leavingToolBarBg);
rootTransition.add(leavingToolBar);
// fade out leaving toolbar items
leavingBackButton.fromTo(OPACITY, 0.99, 0);
leavingTitle.fromTo(OPACITY, 0.99, 0);
leavingToolBarButtons.fromTo(OPACITY, 0.99, 0, 0);
leavingToolBarItems.fromTo(OPACITY, 0.99, 0);
if (backDirection) {
// leaving toolbar, back direction
leavingTitle.fromTo(TRANSLATEX, CENTER, (isRTL ? '-100%' : '100%'));
leavingToolBarItems.fromTo(TRANSLATEX, CENTER, (isRTL ? '-100%' : '100%'));
// leaving toolbar, back direction, and there's no entering toolbar
// should just slide out, no fading out
leavingToolBarBg
.beforeClearStyles([OPACITY])
.fromTo(OPACITY, 1, 0.01);
if (backButtonEl) {
const leavingBackBtnText = new AnimationC();
leavingBackBtnText.addElement(shadow(backButtonEl).querySelector('.button-text'));
leavingBackBtnText.fromTo(TRANSLATEX, CENTER, (isRTL ? -124 : 124) + 'px');
leavingToolBar.add(leavingBackBtnText);
}
} else {
// leaving toolbar, forward direction
leavingTitle
.fromTo(TRANSLATEX, CENTER, OFF_LEFT)
.afterClearStyles([TRANSFORM]);
leavingToolBarItems
.fromTo(TRANSLATEX, CENTER, OFF_LEFT)
.afterClearStyles([TRANSFORM, OPACITY]);
leavingBackButton.afterClearStyles([OPACITY]);
leavingTitle.afterClearStyles([OPACITY]);
leavingToolBarButtons.afterClearStyles([OPACITY]);
}
});
return rootAnimation;
} catch (err) {
throw err;
}
// Return the rootTransition promise
return Promise.resolve(rootTransition);
};