mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
add more tests, fix onFinish functionality, being testing with nav transitions
This commit is contained in:
@@ -953,13 +953,14 @@ export class Nav implements NavOutlet {
|
||||
}
|
||||
|
||||
private onMove(stepValue: number) {
|
||||
console.log('step', stepValue, this.sbAni);
|
||||
if (this.sbAni) {
|
||||
this.sbAni.progressStep(stepValue);
|
||||
(this.sbAni as any).playStep(stepValue);
|
||||
}
|
||||
}
|
||||
|
||||
private onEnd(shouldComplete: boolean, stepValue: number, dur: number) {
|
||||
if (this.sbAni) {
|
||||
if (this.sbAni && this.sbAni.progressEnd) {
|
||||
this.sbAni.progressEnd(shouldComplete, stepValue, dur);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,14 @@ export interface Animation {
|
||||
afterRemoveClasses: string[];
|
||||
afterStylesValue: { [property: string]: any };
|
||||
|
||||
animationFinish(): void;
|
||||
|
||||
play(): Animation;
|
||||
playStep(step: number): Animation;
|
||||
pause(): Animation;
|
||||
stop(): Animation;
|
||||
destroy(): Animation;
|
||||
progressStart(): Animation;
|
||||
|
||||
from(property: string, value: any): Animation;
|
||||
to(property: string, value: any): Animation;
|
||||
@@ -25,7 +28,7 @@ export interface Animation {
|
||||
|
||||
addAnimation(animationToADd: Animation | Animation[] | undefined | null): Animation;
|
||||
addTarget(target: string): Animation;
|
||||
addElement(el: Node | Node[] | NodeList | undefined | null): Animation;
|
||||
addElement(el: Element | Element[] | Node | Node[] | NodeList | undefined | null): Animation;
|
||||
iterations(iterations: number): Animation;
|
||||
fill(fill: 'auto' | 'none' | 'forwards' | 'backwards' | 'both' | undefined): Animation;
|
||||
direction(direction: 'normal' | 'reverse' | 'alternate' | 'alternate-reverse' | undefined): Animation;
|
||||
@@ -159,6 +162,8 @@ export const createAnimation = (animationNameValue: string | undefined): Animati
|
||||
let webAnimations: any[] = [];
|
||||
let onFinishCallback: any | undefined;
|
||||
|
||||
let numAnimationsRunning = 0;
|
||||
|
||||
/**
|
||||
* Destroy this animation and all child animations.
|
||||
*/
|
||||
@@ -203,6 +208,8 @@ export const createAnimation = (animationNameValue: string | undefined): Animati
|
||||
element.style.removeProperty('animation-iteration-count');
|
||||
element.style.removeProperty('animation-delay');
|
||||
element.style.removeProperty('animation-play-state');
|
||||
element.style.removeProperty('animation-fill-mode');
|
||||
element.style.removeProperty('animation-direction');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -394,7 +401,7 @@ export const createAnimation = (animationNameValue: string | undefined): Animati
|
||||
return generatePublicAPI();
|
||||
};
|
||||
|
||||
const addElement = (el: Node | Node[] | NodeList | undefined | null): Animation => {
|
||||
const addElement = (el: Element | Element[] | Node | Node[] | NodeList | undefined | null): Animation => {
|
||||
if (el != null) {
|
||||
const nodeList = el as NodeList;
|
||||
if (nodeList.length >= 0) {
|
||||
@@ -481,77 +488,84 @@ export const createAnimation = (animationNameValue: string | undefined): Animati
|
||||
}
|
||||
};
|
||||
|
||||
const initializeAnimation = () => {
|
||||
beforeAnimation();
|
||||
const animationFinish = () => {
|
||||
if (numAnimationsRunning === 0) { return; }
|
||||
|
||||
if (supportsWebAnimations()) {
|
||||
elements.forEach((element, i) => {
|
||||
const animation = element.animate(getKeyframes(), {
|
||||
delay: getDelay(),
|
||||
duration: getDuration(),
|
||||
easing: getEasing(),
|
||||
iterations: getIterations(),
|
||||
fill: getFill(),
|
||||
direction: getDirection()
|
||||
});
|
||||
numAnimationsRunning--;
|
||||
|
||||
if (i === 0) {
|
||||
animation.onfinish = () => {
|
||||
afterAnimation();
|
||||
};
|
||||
}
|
||||
if (numAnimationsRunning === 0) {
|
||||
afterAnimation();
|
||||
}
|
||||
|
||||
animation.pause();
|
||||
if (parentAnimation) {
|
||||
parentAnimation.animationFinish();
|
||||
}
|
||||
};
|
||||
|
||||
webAnimations.push(animation);
|
||||
});
|
||||
|
||||
} else {
|
||||
|
||||
if (!stylesheet) {
|
||||
const initializeCSSAnimation = () => {
|
||||
if (!stylesheet) {
|
||||
stylesheet = createKeyframeStylesheet(generateKeyframeString(_name, _keyframes));
|
||||
}
|
||||
|
||||
const animationDuration = getDuration();
|
||||
const animationEasing = getEasing();
|
||||
const animationIterationCount = getIterations();
|
||||
const animationDelay = getDelay();
|
||||
const animationFill = getFill();
|
||||
const animationDirection = getDirection();
|
||||
elements.forEach(element => {
|
||||
element.style.setProperty('animation-name', _name || null);
|
||||
element.style.setProperty('animation-duration', (getDuration() !== undefined) ? `${getDuration()}ms` : null);
|
||||
element.style.setProperty('animation-timing-function', getEasing() || null);
|
||||
element.style.setProperty('animation-delay', (getDelay() !== undefined) ? `${getDelay()}ms` : null);
|
||||
element.style.setProperty('animation-fill-mode', getFill() || null);
|
||||
element.style.setProperty('animation-direction', getDirection() || null);
|
||||
|
||||
elements.forEach(element => {
|
||||
if (_name !== undefined) {
|
||||
element.style.setProperty('animation-name', _name);
|
||||
let iterationsCount = null;
|
||||
if (getIterations() !== undefined) {
|
||||
iterationsCount = (getIterations() === Infinity) ? 'infinite' : getIterations()!.toString();
|
||||
}
|
||||
|
||||
if (animationDuration !== undefined) {
|
||||
element.style.setProperty('animation-duration', `${animationDuration}ms`);
|
||||
}
|
||||
|
||||
if (animationEasing !== undefined) {
|
||||
element.style.setProperty('animation-timing-function', animationEasing);
|
||||
}
|
||||
|
||||
if (animationIterationCount !== undefined) {
|
||||
element.style.setProperty('animation-iteration-count', (animationIterationCount === Infinity) ? 'infinite' : animationIterationCount.toString());
|
||||
}
|
||||
|
||||
if (animationDelay !== undefined) {
|
||||
element.style.setProperty('animation-delay', `${animationDelay}ms`);
|
||||
}
|
||||
|
||||
if (animationFill !== undefined) {
|
||||
element.style.setProperty('animation-fill-mode', animationFill);
|
||||
}
|
||||
|
||||
if (animationDirection !== undefined) {
|
||||
element.style.setProperty('animation-direction', animationDirection);
|
||||
}
|
||||
element.style.setProperty('animation-directiteration-countion', iterationsCount);
|
||||
});
|
||||
|
||||
animationEnd(elements[0], () => {
|
||||
afterAnimation();
|
||||
if (elements.length > 0) {
|
||||
animationEnd(elements[0], () => {
|
||||
animationFinish();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const initializeWebAnimation = () => {
|
||||
elements.forEach(element => {
|
||||
const animation = element.animate(getKeyframes(), {
|
||||
delay: getDelay(),
|
||||
duration: getDuration(),
|
||||
easing: getEasing(),
|
||||
iterations: getIterations(),
|
||||
fill: getFill(),
|
||||
direction: getDirection()
|
||||
});
|
||||
|
||||
animation.pause();
|
||||
|
||||
webAnimations.push(animation);
|
||||
});
|
||||
|
||||
if (webAnimations.length > 0) {
|
||||
webAnimations[0].onfinish = () => {
|
||||
animationFinish();
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const initializeAnimation = () => {
|
||||
beforeAnimation();
|
||||
|
||||
numAnimationsRunning = childAnimations.length + 1;
|
||||
|
||||
if (getKeyframes().length === 0) {
|
||||
animationFinish();
|
||||
} else {
|
||||
if (supportsWebAnimations()) {
|
||||
initializeWebAnimation();
|
||||
} else {
|
||||
initializeCSSAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
@@ -562,10 +576,7 @@ export const createAnimation = (animationNameValue: string | undefined): Animati
|
||||
animation.playStep(step);
|
||||
});
|
||||
|
||||
if (!initialized) {
|
||||
initializeAnimation();
|
||||
}
|
||||
|
||||
progressStart();
|
||||
pause();
|
||||
|
||||
if (getDuration() !== undefined) {
|
||||
@@ -610,9 +621,7 @@ export const createAnimation = (animationNameValue: string | undefined): Animati
|
||||
animation.play();
|
||||
});
|
||||
|
||||
if (!initialized) {
|
||||
initializeAnimation();
|
||||
}
|
||||
progressStart(true);
|
||||
|
||||
if (supportsWebAnimations()) {
|
||||
webAnimations.forEach(animation => {
|
||||
@@ -663,7 +672,7 @@ export const createAnimation = (animationNameValue: string | undefined): Animati
|
||||
|
||||
const to = (property: string, value: any): Animation => {
|
||||
const keyframeValues = getKeyframes();
|
||||
const lastFrame = keyframeValues[keyframes.length - 1];
|
||||
const lastFrame = keyframeValues[keyframeValues.length - 1];
|
||||
|
||||
if (lastFrame != null && (lastFrame.offset === undefined || lastFrame.offset === 1)) {
|
||||
lastFrame[property] = value;
|
||||
@@ -686,6 +695,19 @@ export const createAnimation = (animationNameValue: string | undefined): Animati
|
||||
return from(property, fromValue).to(property, toValue);
|
||||
};
|
||||
|
||||
const progressStart = (reset = false): Animation => {
|
||||
if (initialized && reset) {
|
||||
initialized = false;
|
||||
cleanUpElements();
|
||||
}
|
||||
|
||||
if (!initialized) {
|
||||
initializeAnimation();
|
||||
}
|
||||
|
||||
return generatePublicAPI();
|
||||
};
|
||||
|
||||
const generatePublicAPI = (): Animation => {
|
||||
return {
|
||||
parentAnimation,
|
||||
@@ -697,6 +719,9 @@ export const createAnimation = (animationNameValue: string | undefined): Animati
|
||||
afterAddClasses,
|
||||
afterRemoveClasses,
|
||||
afterStylesValue,
|
||||
|
||||
animationFinish,
|
||||
|
||||
from,
|
||||
to,
|
||||
fromTo,
|
||||
@@ -733,6 +758,7 @@ export const createAnimation = (animationNameValue: string | undefined): Animati
|
||||
beforeRemoveClass,
|
||||
beforeAddClass,
|
||||
onFinish,
|
||||
progressStart
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ describe('Animation Class', () => {
|
||||
})
|
||||
});
|
||||
|
||||
describe('keyframes()', () => {
|
||||
describe('Animation Keyframes', () => {
|
||||
let animation;
|
||||
beforeEach(() => {
|
||||
animation = createAnimation('my-animation');
|
||||
@@ -275,8 +275,9 @@ describe('Animation Class', () => {
|
||||
|
||||
it('should get parent easing when child easing is not set', () => {
|
||||
const childAnimation = createAnimation();
|
||||
animation.addAnimation(childAnimation);
|
||||
childAnimation.easing('linear');
|
||||
animation
|
||||
.addAnimation(childAnimation)
|
||||
.easing('linear');
|
||||
|
||||
expect(childAnimation.getEasing()).toEqual('linear');
|
||||
});
|
||||
@@ -285,8 +286,9 @@ describe('Animation Class', () => {
|
||||
const childAnimation = createAnimation();
|
||||
childAnimation.easing('linear');
|
||||
|
||||
animation.addAnimation(childAnimation);
|
||||
animation.easing('ease-in-out');
|
||||
animation
|
||||
.addAnimation(childAnimation)
|
||||
.easing('ease-in-out');
|
||||
|
||||
expect(childAnimation.getEasing()).toEqual('linear');
|
||||
});
|
||||
@@ -297,8 +299,9 @@ describe('Animation Class', () => {
|
||||
|
||||
it('should get parent duration when child duration is not set', () => {
|
||||
const childAnimation = createAnimation();
|
||||
animation.addAnimation(childAnimation);
|
||||
childAnimation.duration(500);
|
||||
animation
|
||||
.addAnimation(childAnimation)
|
||||
.duration(500);
|
||||
|
||||
expect(childAnimation.getDuration()).toEqual(500);
|
||||
});
|
||||
@@ -307,8 +310,9 @@ describe('Animation Class', () => {
|
||||
const childAnimation = createAnimation();
|
||||
childAnimation.duration(500);
|
||||
|
||||
animation.addAnimation(childAnimation);
|
||||
animation.duration(1000);
|
||||
animation
|
||||
.addAnimation(childAnimation)
|
||||
.duration(1000);
|
||||
|
||||
expect(childAnimation.getDuration()).toEqual(500);
|
||||
});
|
||||
@@ -319,8 +323,9 @@ describe('Animation Class', () => {
|
||||
|
||||
it('should get parent delay when child delay is not set', () => {
|
||||
const childAnimation = createAnimation();
|
||||
animation.addAnimation(childAnimation);
|
||||
childAnimation.delay(500);
|
||||
animation
|
||||
.addAnimation(childAnimation)
|
||||
.delay(500);
|
||||
|
||||
expect(childAnimation.getDelay()).toEqual(500);
|
||||
});
|
||||
@@ -329,8 +334,9 @@ describe('Animation Class', () => {
|
||||
const childAnimation = createAnimation();
|
||||
childAnimation.delay(500);
|
||||
|
||||
animation.addAnimation(childAnimation);
|
||||
animation.delay(1000);
|
||||
animation
|
||||
.addAnimation(childAnimation)
|
||||
.delay(1000);
|
||||
|
||||
expect(childAnimation.getDelay()).toEqual(500);
|
||||
});
|
||||
@@ -341,8 +347,9 @@ describe('Animation Class', () => {
|
||||
|
||||
it('should get parent iterations when child iterations is not set', () => {
|
||||
const childAnimation = createAnimation();
|
||||
animation.addAnimation(childAnimation);
|
||||
childAnimation.iterations(2);
|
||||
animation
|
||||
.addAnimation(childAnimation)
|
||||
.iterations(2);
|
||||
|
||||
expect(childAnimation.getIterations()).toEqual(2);
|
||||
});
|
||||
@@ -351,10 +358,59 @@ describe('Animation Class', () => {
|
||||
const childAnimation = createAnimation();
|
||||
childAnimation.iterations(2);
|
||||
|
||||
animation.addAnimation(childAnimation);
|
||||
animation.iterations(1);
|
||||
animation
|
||||
.addAnimation(childAnimation)
|
||||
.iterations(1);
|
||||
|
||||
expect(childAnimation.getIterations()).toEqual(2);
|
||||
});
|
||||
|
||||
it('should get undefined when fill not set', () => {
|
||||
expect(animation.getFill()).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('should get parent fill when child fill is not set', () => {
|
||||
const childAnimation = createAnimation();
|
||||
animation
|
||||
.addAnimation(childAnimation)
|
||||
.fill('both');
|
||||
|
||||
expect(childAnimation.getFill()).toEqual('both');
|
||||
});
|
||||
|
||||
it('should get prefer child fill over parent fill', () => {
|
||||
const childAnimation = createAnimation();
|
||||
childAnimation.fill('none');
|
||||
|
||||
animation
|
||||
.addAnimation(childAnimation)
|
||||
.fill('forwards');
|
||||
|
||||
expect(childAnimation.getFill()).toEqual('none');
|
||||
});
|
||||
|
||||
it('should get undefined when direction not set', () => {
|
||||
expect(animation.getDirection()).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('should get parent direction when child direction is not set', () => {
|
||||
const childAnimation = createAnimation();
|
||||
animation
|
||||
.addAnimation(childAnimation)
|
||||
.direction('alternate');
|
||||
|
||||
expect(childAnimation.getDirection()).toEqual('alternate');
|
||||
});
|
||||
|
||||
it('should get prefer child direction over parent direction', () => {
|
||||
const childAnimation = createAnimation();
|
||||
childAnimation.direction('alternate-reverse');
|
||||
|
||||
animation
|
||||
.addAnimation(childAnimation)
|
||||
.direction('normal');
|
||||
|
||||
expect(childAnimation.getDirection()).toEqual('alternate-reverse');
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
28
core/src/utils/animation/test/basic/e2e.ts
Normal file
28
core/src/utils/animation/test/basic/e2e.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
import { listenForEvent, waitForFunctionTestContext } from '../../../test/utils';
|
||||
|
||||
test.only(`animation: basic`, async () => {
|
||||
const page = await newE2EPage({ url: '/src/utils/animation/test/basic?ionic:_testing=true' });
|
||||
const screenshotCompares = [];
|
||||
|
||||
screenshotCompares.push(await page.compareScreenshot());
|
||||
|
||||
const ANIMATION_FINISHED = 'onIonAnimationFinished';
|
||||
const animationFinishedCount: any = { count: 0 };
|
||||
await page.exposeFunction(ANIMATION_FINISHED, () => {
|
||||
animationFinishedCount.count += 1;
|
||||
});
|
||||
|
||||
const square = await page.$('.square-a');
|
||||
await listenForEvent(page, 'ionAnimationFinished', square, ANIMATION_FINISHED);
|
||||
|
||||
await page.click('.play');
|
||||
await page.waitForSelector('.play');
|
||||
|
||||
await waitForFunctionTestContext((payload: any) => {
|
||||
return payload.animationFinishedCount.count === 1;
|
||||
}, { animationFinishedCount });
|
||||
|
||||
screenshotCompares.push(await page.compareScreenshot());
|
||||
});
|
||||
@@ -13,127 +13,42 @@
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
<script type="module">
|
||||
import { createAnimation } from '../../../../dist/collection/utils/animation/animation.js';
|
||||
import { createGesture } from '../../../../dist/ionic/index-a098653b.js';
|
||||
|
||||
function clamp(val) {
|
||||
if (val > 1) {
|
||||
return 1;
|
||||
} else if (val < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
const squareA = document.querySelectorAll('.square-a');
|
||||
const squareB = document.querySelectorAll('.square-b');
|
||||
const squareC = document.querySelectorAll('.square-c');
|
||||
|
||||
const track = document.querySelector('.track');
|
||||
const cursor = document.querySelector('.track .cursor');
|
||||
|
||||
|
||||
const squareA = document.querySelector('.square-a');
|
||||
|
||||
const rootAnimation = createAnimation();
|
||||
const animationA = createAnimation('animation-a');
|
||||
const animationB = createAnimation('animation-b');
|
||||
const animationC = createAnimation('animation-c');
|
||||
|
||||
const gesture = createGesture({
|
||||
el: track,
|
||||
gestureName: 'drag',
|
||||
gesturePriority: 100,
|
||||
treshold: 5,
|
||||
onStart: () => {},
|
||||
onMove: (ev) => {
|
||||
const start = ev.startX;
|
||||
const width = track.clientWidth;
|
||||
|
||||
const lower = start;
|
||||
const upper = width - start;
|
||||
|
||||
const current = clamp((ev.currentX - lower) / upper);
|
||||
|
||||
rootAnimation.playStep(current);
|
||||
cursor.style.transform = `translateX(${ev.currentX - 10}px)`;
|
||||
},
|
||||
onEnd: (ev) => {
|
||||
rootAnimation.playStep(0);
|
||||
cursor.style.transform = `translateX(${0}px)`;
|
||||
}
|
||||
});
|
||||
|
||||
gesture.setDisabled(false);
|
||||
|
||||
animationA
|
||||
rootAnimation
|
||||
.addElement(squareA)
|
||||
.duration(2000)
|
||||
.delay(5000)
|
||||
.duration(1000)
|
||||
.easing('linear')
|
||||
.iterations(Infinity)
|
||||
.keyframes([
|
||||
{ transform: 'scale(1) rotate(0deg)', opacity: 1, offset: 0 },
|
||||
{ transform: 'scale(1.5) rotate(45deg)', opacity: 0.5, offset: 0.5 },
|
||||
{ transform: 'scale(1) rotate(0deg)', opacity: 1, offset: 1 }
|
||||
]);
|
||||
{ background: 'rgba(255, 0, 0, 0.5)', offset: 0 },
|
||||
{ background: 'rgba(0, 255, 0, 0.5)', offset: 0.33 },
|
||||
{ background: 'rgba(0, 0, 255, 0.5)', offset: 0.66 },
|
||||
{ background: 'rgba(255, 0, 0, 0.5)', offset: 1 }
|
||||
])
|
||||
.onFinish(() => {
|
||||
const ev = new CustomEvent('ionAnimationFinished');
|
||||
squareA.dispatchEvent(ev);
|
||||
});
|
||||
|
||||
animationB
|
||||
.addElement(squareB)
|
||||
.duration(500)
|
||||
.delay(2000)
|
||||
.easing('ease-in-out')
|
||||
.iterations(Infinity)
|
||||
.keyframes([
|
||||
{ transform: 'scale(1) rotate(0deg)', opacity: 1, offset: 0 },
|
||||
{ transform: 'scale(0.5) rotate(-45deg)', opacity: 0.5, offset: 0.5 },
|
||||
{ transform: 'scale(1) rotate(0deg)', opacity: 1, offset: 1 }
|
||||
]);
|
||||
|
||||
animationC
|
||||
.addElement(squareC)
|
||||
.duration(2000)
|
||||
.delay(500)
|
||||
.easing('ease-in-out')
|
||||
.iterations(Infinity)
|
||||
.keyframes([
|
||||
{ transform: 'scale(1) skew(0deg)', opacity: 1, offset: 0 },
|
||||
{ transform: 'scale(1.5) skew(15deg)', opacity: 0.5, offset: 0.5 },
|
||||
{ transform: 'scale(1) skew(0deg)', opacity: 1, offset: 1 }
|
||||
]);
|
||||
|
||||
rootAnimation.addAnimation([animationA, animationB, animationC]);
|
||||
//rootAnimation.play();
|
||||
document.querySelector('.play').addEventListener('click', () => {
|
||||
rootAnimation.play();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.square {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: rgba(0, 0, 255, 0.5);
|
||||
background: rgba(255, 0, 0, 0.5);
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
margin-left: 25px;
|
||||
margin-top: 25px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.track {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
background: rgba(0, 255, 0, 0.5);
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
user-select: none;
|
||||
cursor: ew-resize;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.track .cursor {
|
||||
pointer-events: none;
|
||||
width: 10px;
|
||||
height: 100px;
|
||||
background: rgba(255, 0, 0, 0.5);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -147,21 +62,14 @@
|
||||
|
||||
<ion-content>
|
||||
<div class="ion-padding">
|
||||
<div class="track">
|
||||
Drag along the track to animate the elements
|
||||
<div class="cursor"></div>
|
||||
</div>
|
||||
<ion-button class="play">Play</ion-button>
|
||||
<ion-button class="pause">Pause</ion-button>
|
||||
<ion-button class="stop">Stop</ion-button>
|
||||
<ion-button class="destroy">Destroy</ion-button>
|
||||
|
||||
<div class="square square-a">
|
||||
Hello
|
||||
</div>
|
||||
|
||||
<div class="square square-b">
|
||||
Hello
|
||||
</div>
|
||||
|
||||
<div class="square square-c">
|
||||
Hello
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
|
||||
227
core/src/utils/animation/test/chaining/index.html
Normal file
227
core/src/utils/animation/test/chaining/index.html
Normal file
@@ -0,0 +1,227 @@
|
||||
<!DOCTYPE html>
|
||||
<html dir="ltr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Animation - Basic</title>
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
<script type="module">
|
||||
import { createAnimation } from '../../../../dist/collection/utils/animation/animation.js';
|
||||
|
||||
const squareA = document.querySelectorAll('.square-a');
|
||||
const squareB = document.querySelectorAll('.square-b');
|
||||
const squareC = document.querySelectorAll('.square-c');
|
||||
const squareCText = document.querySelectorAll('.square-c .text');
|
||||
|
||||
const animationA = createAnimation('animation-a');
|
||||
const animationB = createAnimation('animation-b');
|
||||
const animationC = createAnimation('animation-c');
|
||||
|
||||
const animationCSubA = createAnimation('animation-c-sub-a');
|
||||
const animationCSubB = createAnimation('animation-c-sub-b');
|
||||
|
||||
animationA
|
||||
.addElement(squareA)
|
||||
.duration(1000)
|
||||
.easing('linear')
|
||||
.iterations(1)
|
||||
.keyframes([
|
||||
{ transform: 'scale(1) rotate(0deg)', opacity: 1, offset: 0 },
|
||||
{ transform: 'scale(1.5) rotate(45deg)', opacity: 0.5, offset: 0.5 },
|
||||
{ transform: 'scale(1) rotate(0deg)', opacity: 1, offset: 1 }
|
||||
])
|
||||
.beforeStyles({
|
||||
'background': 'rgba(0, 0, 255, 0.5'
|
||||
})
|
||||
.afterStyles({
|
||||
'background': 'rgba(0, 255, 0, 0.5)'
|
||||
});
|
||||
|
||||
animationB
|
||||
.addElement(squareB)
|
||||
.duration(500)
|
||||
.easing('ease-in-out')
|
||||
.iterations(1)
|
||||
.keyframes([
|
||||
{ transform: 'scale(1) rotate(0deg)', opacity: 1, offset: 0 },
|
||||
{ transform: 'scale(0.5) rotate(-45deg)', opacity: 0.5, offset: 0.5 },
|
||||
{ transform: 'scale(1) rotate(0deg)', opacity: 1, offset: 1 }
|
||||
])
|
||||
.beforeStyles({
|
||||
'background': 'rgba(0, 0, 255, 0.5'
|
||||
})
|
||||
.afterStyles({
|
||||
'background': 'rgba(0, 255, 0, 0.5)'
|
||||
});
|
||||
|
||||
animationCSubA
|
||||
.addElement(squareCText)
|
||||
.duration(1000)
|
||||
.delay(2000)
|
||||
.keyframes([
|
||||
{
|
||||
offset: 0,
|
||||
transform: 'scale(1)'
|
||||
},
|
||||
{
|
||||
offset: 0.5,
|
||||
transform: 'scale(1.5)'
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
transform: 'scale(1)'
|
||||
}
|
||||
])
|
||||
.fromTo('color', 'red', 'blue')
|
||||
.onFinish(() => {
|
||||
document.querySelector('li.animation-c-sub-a .status').innerText = 'DONE';
|
||||
});
|
||||
|
||||
animationCSubB
|
||||
.addElement(squareCText)
|
||||
.duration(1000)
|
||||
.delay(3500)
|
||||
.fromTo('background', 'red', 'blue')
|
||||
.onFinish(() => {
|
||||
document.querySelector('li.animation-c-sub-b .status').innerText = 'DONE';
|
||||
});
|
||||
|
||||
animationC
|
||||
.addElement(squareC)
|
||||
.duration(2000)
|
||||
.easing('ease-in-out')
|
||||
.iterations(1)
|
||||
.keyframes([
|
||||
{ transform: 'scale(1) skew(0deg)', opacity: 1, offset: 0 },
|
||||
{ transform: 'scale(1.5) skew(15deg)', opacity: 0.5, offset: 0.5 },
|
||||
{ transform: 'scale(1) skew(0deg)', opacity: 1, offset: 1 }
|
||||
])
|
||||
.beforeStyles({
|
||||
'background': 'rgba(0, 0, 255, 0.5'
|
||||
})
|
||||
.afterStyles({
|
||||
'background': 'rgba(0, 255, 0, 0.5)'
|
||||
})
|
||||
.addAnimation([animationCSubA, animationCSubB])
|
||||
.onFinish(() => {
|
||||
document.querySelector('li.animation-c .status').innerText = 'DONE';
|
||||
});
|
||||
|
||||
animationA.onFinish(() => {
|
||||
animationB.play();
|
||||
document.querySelector('li.animation-a .status').innerText = 'DONE';
|
||||
});
|
||||
animationB.onFinish(() => {
|
||||
animationC.play()
|
||||
document.querySelector('li.animation-b .status').innerText = 'DONE';
|
||||
});
|
||||
|
||||
document.querySelector('.play').addEventListener('click', () => {
|
||||
animationA.play();
|
||||
document.querySelectorAll('.status').forEach(status => {
|
||||
status.innerText = '';
|
||||
})
|
||||
});
|
||||
|
||||
document.querySelector('.pause').addEventListener('click', () => {
|
||||
animationA.pause();
|
||||
});
|
||||
|
||||
document.querySelector('.destroy').addEventListener('click', () => {
|
||||
animationA.destroy();
|
||||
});
|
||||
|
||||
document.querySelector('.stop').addEventListener('click', () => {
|
||||
animationA.stop();
|
||||
|
||||
document.querySelectorAll('.status').forEach(status => {
|
||||
status.innerText = '';
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.square {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: rgba(0, 0, 255, 0.5);
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
margin-left: 25px;
|
||||
margin-top: 25px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.status-pane {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
width: 300px;
|
||||
height: 400px;
|
||||
padding-right: 10px;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
li .status {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Animations</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<div class="ion-padding">
|
||||
<ion-button class="play">Play</ion-button>
|
||||
<ion-button class="pause">Pause</ion-button>
|
||||
<ion-button class="stop">Stop</ion-button>
|
||||
<ion-button class="destroy">Destroy</ion-button>
|
||||
|
||||
<div class="square square-a">
|
||||
<div class="text">Hello</div>
|
||||
</div>
|
||||
|
||||
<div class="square square-b">
|
||||
<div class="text">Hello</div>
|
||||
</div>
|
||||
|
||||
<div class="square square-c">
|
||||
<div class="text">Hello</div>
|
||||
</div>
|
||||
|
||||
<div class="status-pane">
|
||||
<ul>
|
||||
<li class="animation-a">Animation A<div class="status"></div></li>
|
||||
</ul>
|
||||
|
||||
<ul>
|
||||
<li class="animation-b">Animation B<div class="status"></div></li>
|
||||
</ul>
|
||||
|
||||
<ul>
|
||||
<li class="animation-c">Animation C<div class="status"></div></li>
|
||||
<ul>
|
||||
<li class="animation-c-sub-a">Animation C sub A<div class="status"></div></li>
|
||||
<li class="animation-c-sub-b">Animation C sub B<div class="status"></div></li>
|
||||
</ul>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
61
core/src/utils/animation/test/gesture/e2e.ts
Normal file
61
core/src/utils/animation/test/gesture/e2e.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
import { checkComponentModeClasses, checkModeClasses } from '../utils';
|
||||
|
||||
// This test is to loop through all components that should have
|
||||
// specific classes added and test them
|
||||
test('component: modes', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/utils/test/modes?ionic:_testing=true'
|
||||
});
|
||||
|
||||
// First test: .button class
|
||||
// ----------------------------------------------------------------
|
||||
// components that need to have the `button` class
|
||||
// for use in styling by other components (`ion-buttons`)
|
||||
// e.g. <ion-back-button class="button">
|
||||
let tags = ['ion-button', 'ion-back-button', 'ion-menu-button'];
|
||||
|
||||
for (const tag of tags) {
|
||||
const el = await page.find(tag);
|
||||
expect(el).toHaveClass('button');
|
||||
}
|
||||
|
||||
// Second test: .item class
|
||||
// ----------------------------------------------------------------
|
||||
// components that need to have the `item` class
|
||||
// for use in styling by other components
|
||||
// e.g. <ion-item-divider class="item">
|
||||
tags = ['ion-item', 'ion-item-divider', 'ion-item-group'];
|
||||
|
||||
for (const tag of tags) {
|
||||
const el = await page.find(tag);
|
||||
expect(el).toHaveClass('item');
|
||||
}
|
||||
|
||||
// Third test: .{component}-{mode} class
|
||||
// ----------------------------------------------------------------
|
||||
// components that need to have their tag name
|
||||
// + mode as a class for internal styling
|
||||
// e.g. <ion-card-content class="card-content-md">
|
||||
tags = ['ion-card-content', 'ion-footer', 'ion-header', 'ion-infinite-scroll-content', 'ion-item-group', 'ion-item-options', 'ion-list', 'ion-picker', 'ion-refresher', 'ion-slides', 'ion-split-pane'];
|
||||
|
||||
for (const tag of tags) {
|
||||
const el = await page.find(tag);
|
||||
await checkComponentModeClasses(el);
|
||||
}
|
||||
|
||||
// Fourth test: .{mode} class
|
||||
// ----------------------------------------------------------------
|
||||
// components that need to have the mode class
|
||||
// added for external / user styling
|
||||
// e.g. <ion-badge class="md">
|
||||
tags = ['ion-action-sheet', 'ion-alert', 'ion-anchor', 'ion-app', 'ion-avatar', 'ion-back-button', 'ion-backdrop', 'ion-badge', 'ion-button', 'ion-buttons', 'ion-card-content', 'ion-card-header', 'ion-card-subtitle', 'ion-card-title', 'ion-card', 'ion-checkbox', 'ion-chip', 'ion-col', 'ion-content', 'ion-datetime', 'ion-fab', 'ion-fab-button', 'ion-fab-list', 'ion-footer', 'ion-grid', 'ion-header', 'ion-icon', 'ion-img', 'ion-infinite-scroll', 'ion-infinite-scroll-content', 'ion-input', 'ion-item', 'ion-item-divider', 'ion-item-group', 'ion-item-option', 'ion-item-options', 'ion-item-sliding', 'ion-label', 'ion-list', 'ion-list-header', 'ion-loading', 'ion-modal', 'ion-menu', 'ion-menu-button', 'ion-menu-toggle', 'ion-note', 'ion-picker', 'ion-picker-column', 'ion-popover', 'ion-progress-bar', 'ion-radio', 'ion-radio-group', 'ion-range', 'ion-refresher', 'ion-refresher-content', 'ion-reorder', 'ion-reorder-group', 'ion-ripple-effect', 'ion-row', 'ion-searchbar', 'ion-segment', 'ion-segment-button', 'ion-select', 'ion-select-option', 'ion-select-popover', 'ion-skeleton-text', 'ion-slide', 'ion-slides', 'ion-spinner', 'ion-split-pane', 'ion-tab-bar', 'ion-tab-button', 'ion-text', 'ion-textarea', 'ion-thumbnail', 'ion-title', 'ion-toast', 'ion-toggle', 'ion-toolbar'];
|
||||
|
||||
const globalMode = await page.evaluate(() => document.documentElement.getAttribute('mode'));
|
||||
for (const tag of tags) {
|
||||
await page.waitForSelector(tag);
|
||||
const el = await page.find(tag);
|
||||
await checkModeClasses(el, globalMode!);
|
||||
}
|
||||
});
|
||||
171
core/src/utils/animation/test/gesture/index.html
Normal file
171
core/src/utils/animation/test/gesture/index.html
Normal file
@@ -0,0 +1,171 @@
|
||||
<!DOCTYPE html>
|
||||
<html dir="ltr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Animation - Basic</title>
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
<script type="module">
|
||||
import { createAnimation } from '../../../../dist/collection/utils/animation/animation.js';
|
||||
import { createGesture } from '../../../../dist/ionic/index-91289ad8.js';
|
||||
|
||||
function clamp(val) {
|
||||
if (val > 1) {
|
||||
return 1;
|
||||
} else if (val < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
const squareA = document.querySelectorAll('.square-a');
|
||||
const squareB = document.querySelectorAll('.square-b');
|
||||
const squareC = document.querySelectorAll('.square-c');
|
||||
|
||||
const track = document.querySelector('.track');
|
||||
const cursor = document.querySelector('.track .cursor');
|
||||
|
||||
const rootAnimation = createAnimation();
|
||||
const animationA = createAnimation('animation-a');
|
||||
const animationB = createAnimation('animation-b');
|
||||
const animationC = createAnimation('animation-c');
|
||||
|
||||
const gesture = createGesture({
|
||||
el: track,
|
||||
gestureName: 'drag',
|
||||
gesturePriority: 100,
|
||||
treshold: 5,
|
||||
onStart: () => {},
|
||||
onMove: (ev) => {
|
||||
const start = ev.startX;
|
||||
const width = track.clientWidth;
|
||||
|
||||
const lower = start;
|
||||
const upper = width - start;
|
||||
|
||||
const current = clamp((ev.currentX - lower) / upper);
|
||||
|
||||
rootAnimation.playStep(current);
|
||||
cursor.style.transform = `translateX(${ev.currentX - 10}px)`;
|
||||
},
|
||||
onEnd: (ev) => {
|
||||
rootAnimation.playStep(0);
|
||||
cursor.style.transform = `translateX(${0}px)`;
|
||||
}
|
||||
});
|
||||
|
||||
gesture.setDisabled(false);
|
||||
|
||||
animationA
|
||||
.addElement(squareA)
|
||||
.duration(2000)
|
||||
.delay(5000)
|
||||
.easing('linear')
|
||||
.iterations(Infinity)
|
||||
.keyframes([
|
||||
{ transform: 'scale(1) rotate(0deg)', opacity: 1, offset: 0 },
|
||||
{ transform: 'scale(1.5) rotate(45deg)', opacity: 0.5, offset: 0.5 },
|
||||
{ transform: 'scale(1) rotate(0deg)', opacity: 1, offset: 1 }
|
||||
]);
|
||||
|
||||
animationB
|
||||
.addElement(squareB)
|
||||
.duration(500)
|
||||
.delay(2000)
|
||||
.easing('ease-in-out')
|
||||
.iterations(Infinity)
|
||||
.keyframes([
|
||||
{ transform: 'scale(1) rotate(0deg)', opacity: 1, offset: 0 },
|
||||
{ transform: 'scale(0.5) rotate(-45deg)', opacity: 0.5, offset: 0.5 },
|
||||
{ transform: 'scale(1) rotate(0deg)', opacity: 1, offset: 1 }
|
||||
]);
|
||||
|
||||
animationC
|
||||
.addElement(squareC)
|
||||
.duration(2000)
|
||||
.delay(500)
|
||||
.easing('ease-in-out')
|
||||
.iterations(Infinity)
|
||||
.keyframes([
|
||||
{ transform: 'scale(1) skew(0deg)', opacity: 1, offset: 0 },
|
||||
{ transform: 'scale(1.5) skew(15deg)', opacity: 0.5, offset: 0.5 },
|
||||
{ transform: 'scale(1) skew(0deg)', opacity: 1, offset: 1 }
|
||||
]);
|
||||
|
||||
rootAnimation.addAnimation([animationA, animationB, animationC]);
|
||||
//rootAnimation.play();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.square {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: rgba(0, 0, 255, 0.5);
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
margin-left: 25px;
|
||||
margin-top: 25px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.track {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
background: rgba(0, 255, 0, 0.5);
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
user-select: none;
|
||||
cursor: ew-resize;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.track .cursor {
|
||||
pointer-events: none;
|
||||
width: 10px;
|
||||
height: 100px;
|
||||
background: rgba(255, 0, 0, 0.5);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Animations</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<div class="ion-padding">
|
||||
<div class="track">
|
||||
Drag along the track to animate the elements
|
||||
<div class="cursor"></div>
|
||||
</div>
|
||||
<div class="square square-a">
|
||||
Hello
|
||||
</div>
|
||||
|
||||
<div class="square square-b">
|
||||
Hello
|
||||
</div>
|
||||
|
||||
<div class="square square-c">
|
||||
Hello
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -17,12 +17,15 @@
|
||||
const squareA = document.querySelectorAll('.square-a');
|
||||
const squareB = document.querySelectorAll('.square-b');
|
||||
const squareC = document.querySelectorAll('.square-c');
|
||||
const squareCText = document.querySelectorAll('.square-c .text');
|
||||
|
||||
const rootAnimation = createAnimation();
|
||||
const animationA = createAnimation('animation-a');
|
||||
const animationB = createAnimation('animation-b');
|
||||
const animationC = createAnimation('animation-c');
|
||||
|
||||
const animationCSubA = createAnimation('animation-c-sub-a');
|
||||
|
||||
animationA
|
||||
.addElement(squareA)
|
||||
.duration(2000)
|
||||
@@ -39,6 +42,9 @@
|
||||
})
|
||||
.afterStyles({
|
||||
'background': 'rgba(0, 255, 0, 0.5)'
|
||||
})
|
||||
.onFinish(() => {
|
||||
console.log('Animation A Done');
|
||||
});
|
||||
|
||||
animationB
|
||||
@@ -57,8 +63,11 @@
|
||||
})
|
||||
.afterStyles({
|
||||
'background': 'rgba(0, 255, 0, 0.5)'
|
||||
})
|
||||
.onFinish(() => {
|
||||
console.log('Animation B Done');
|
||||
});
|
||||
|
||||
|
||||
animationC
|
||||
.addElement(squareC)
|
||||
.duration(2000)
|
||||
@@ -75,9 +84,27 @@
|
||||
})
|
||||
.afterStyles({
|
||||
'background': 'rgba(0, 255, 0, 0.5)'
|
||||
})
|
||||
.onFinish(() => {
|
||||
console.log('Animation C Done')
|
||||
});
|
||||
|
||||
animationCSubA
|
||||
.addElement(squareCText)
|
||||
.duration(1000)
|
||||
.delay(3000)
|
||||
.fromTo('color', 'red', 'blue')
|
||||
.onFinish(() => {
|
||||
console.log('Animation CSubA Done');
|
||||
});
|
||||
|
||||
animationC.addAnimation(animationCSubA);
|
||||
|
||||
rootAnimation.addAnimation([animationA, animationB, animationC]);
|
||||
rootAnimation
|
||||
.addAnimation([animationA, animationB, animationC])
|
||||
.onFinish(() => {
|
||||
console.log('Root Animation Done');
|
||||
});
|
||||
|
||||
document.querySelector('.play').addEventListener('click', () => {
|
||||
rootAnimation.play();
|
||||
@@ -126,15 +153,15 @@
|
||||
<ion-button class="destroy">Destroy</ion-button>
|
||||
|
||||
<div class="square square-a">
|
||||
Hello
|
||||
<div class="text">Hello</div>
|
||||
</div>
|
||||
|
||||
<div class="square square-b">
|
||||
Hello
|
||||
<div class="text">Hello</div>
|
||||
</div>
|
||||
|
||||
<div class="square square-c">
|
||||
Hello
|
||||
<div class="text">Hello</div>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
|
||||
@@ -77,18 +77,32 @@ const animation = async (animationBuilder: AnimationBuilder, opts: TransitionOpt
|
||||
await waitForReady(opts, true);
|
||||
|
||||
const trans = await import('../animation').then(mod => mod.create(animationBuilder, opts.baseEl, opts));
|
||||
trans;
|
||||
|
||||
fireWillEvents(opts.enteringEl, opts.leavingEl);
|
||||
await playTransition(trans, opts);
|
||||
|
||||
const animation = ((opts.mode === 'ios')
|
||||
? (await iosTransitionAnimation()).newIosTransitionAnimation(opts.baseEl, opts)
|
||||
: (await mdTransitionAnimation()).newMdTransitionAnimation(opts) as any);
|
||||
|
||||
animation;
|
||||
|
||||
await playTransition(animation, opts);
|
||||
|
||||
animation.hasCompleted = true;
|
||||
|
||||
if (opts.progressCallback) {
|
||||
opts.progressCallback(undefined);
|
||||
}
|
||||
|
||||
if (trans.hasCompleted) {
|
||||
if (animation.hasCompleted) {
|
||||
fireDidEvents(opts.enteringEl, opts.leavingEl);
|
||||
}
|
||||
|
||||
console.log('done!');
|
||||
return {
|
||||
hasCompleted: trans.hasCompleted,
|
||||
animation: trans
|
||||
hasCompleted: animation.hasCompleted,
|
||||
animation
|
||||
};
|
||||
};
|
||||
|
||||
@@ -126,7 +140,7 @@ const notifyViewReady = async (viewIsReady: undefined | ((enteringEl: HTMLElemen
|
||||
}
|
||||
};
|
||||
|
||||
const playTransition = (trans: Animation, opts: TransitionOptions): Promise<Animation> => {
|
||||
const playTransition = async (trans: any, opts: TransitionOptions): Promise<Animation> => {
|
||||
const progressCallback = opts.progressCallback;
|
||||
const promise = new Promise<Animation>(resolve => trans.onFinish(resolve));
|
||||
|
||||
@@ -135,7 +149,7 @@ const playTransition = (trans: Animation, opts: TransitionOptions): Promise<Anim
|
||||
// this is a swipe to go back, just get the transition progress ready
|
||||
// kick off the swipe animation start
|
||||
trans.progressStart();
|
||||
progressCallback(trans);
|
||||
progressCallback(trans as any);
|
||||
|
||||
} else {
|
||||
// only the top level transition should actually start "play"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Animation } from '../../interface';
|
||||
import { Animation as AnimationNew, createAnimation } from '../animation/animation';
|
||||
import { TransitionOptions } from '../transition';
|
||||
|
||||
const DURATION = 500;
|
||||
@@ -13,6 +14,212 @@ export const shadow = <T extends Element>(el: T): ShadowRoot | T => {
|
||||
return el.shadowRoot || el;
|
||||
};
|
||||
|
||||
export const newIosTransitionAnimation = (navEl: HTMLElement, opts: TransitionOptions): Promise<AnimationNew> => {
|
||||
try {
|
||||
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 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 rootAnimation = createAnimation('ios-root-animation');
|
||||
const enteringContentAnimation = createAnimation('ios-entering-content-animation');
|
||||
|
||||
rootAnimation
|
||||
.addElement(enteringEl)
|
||||
.duration(opts.duration || DURATION)
|
||||
.easing(opts.easing || EASING)
|
||||
.beforeRemoveClass('ion-page-invisible');
|
||||
|
||||
if (leavingEl && navEl) {
|
||||
const navDecorAnimation = createAnimation('ios-decor-animation');
|
||||
navDecorAnimation.addElement(navEl);
|
||||
rootAnimation.addAnimation(navDecorAnimation);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
rootAnimation.addAnimation(enteringContentAnimation);
|
||||
|
||||
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})`);
|
||||
}
|
||||
|
||||
enteringToolBarEls.forEach((enteringToolBarEl, i) => {
|
||||
const enteringToolBar = createAnimation(`ios-entering-toolbar-${i}`);
|
||||
enteringToolBar.addElement(enteringToolBarEl);
|
||||
rootAnimation.addAnimation(enteringToolBar);
|
||||
|
||||
const enteringTitle = createAnimation(`ios-entering-toolbar-${i}-title`);
|
||||
enteringTitle.addElement(enteringToolBarEl.querySelector('ion-title'));
|
||||
|
||||
const enteringToolBarButtons = createAnimation(`ios-entering-toolbar-${i}-buttons`);
|
||||
enteringToolBarButtons.addElement(enteringToolBarEl.querySelectorAll('ion-buttons,[menuToggle]'));
|
||||
|
||||
const enteringToolBarItems = createAnimation(`ios-entering-toolbar-${i}-items`);
|
||||
enteringToolBarItems.addElement(enteringToolBarEl.querySelectorAll(':scope > *:not(ion-title):not(ion-buttons):not([menuToggle])'));
|
||||
|
||||
const enteringToolBarBg = createAnimation(`ios-entering-toolbar-${i}-bg`);
|
||||
enteringToolBarBg.addElement(shadow(enteringToolBarEl).querySelector('.toolbar-background'));
|
||||
|
||||
const enteringBackButton = createAnimation(`ios-entering-toolbar-${i}-back-button`);
|
||||
const backButtonEl = enteringToolBarEl.querySelector('ion-back-button');
|
||||
|
||||
if (backButtonEl) {
|
||||
enteringBackButton.addElement(backButtonEl);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (backDirection) {
|
||||
enteringTitle.fromTo('transform', `translateX(${OFF_LEFT})`, `translateX(${CENTER})`);
|
||||
|
||||
enteringToolBarItems.fromTo('transform', `translateX(${OFF_LEFT})`, `translateX(${CENTER})`);
|
||||
|
||||
// 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(`ios-entering-toolbar-${i}-back-button-text`);
|
||||
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(`ios-leaving-content-animation`);
|
||||
leavingContent.addElement(leavingEl.querySelector(':scope > ion-content'));
|
||||
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);
|
||||
}
|
||||
|
||||
const leavingToolBarEls = leavingEl.querySelectorAll(':scope > ion-header > ion-toolbar');
|
||||
leavingToolBarEls.forEach((leavingToolBarEl, i) => {
|
||||
const leavingToolBar = createAnimation(`ios-leaving-toolbar-${i}-animation`);
|
||||
leavingToolBar.addElement(leavingToolBarEl);
|
||||
|
||||
const leavingTitle = createAnimation(`ios-leaving-toolbar-${i}-title-animation`);
|
||||
leavingTitle.addElement(leavingToolBarEl.querySelector('ion-title'));
|
||||
|
||||
const leavingToolBarButtons = createAnimation(`ios-leaving-toolbar-${i}-buttons-animation`);
|
||||
leavingToolBarButtons.addElement(leavingToolBarEl.querySelectorAll('ion-buttons,[menuToggle]'));
|
||||
|
||||
const leavingToolBarItems = createAnimation(`ios-leaving-toolbar-${i}-items-animation`);
|
||||
const leavingToolBarItemEls = leavingToolBarEl.querySelectorAll(':scope > *:not(ion-title):not(ion-buttons):not([menuToggle])');
|
||||
if (leavingToolBarItemEls.length > 0) {
|
||||
leavingToolBarItems.addElement(leavingToolBarItemEls);
|
||||
}
|
||||
|
||||
const leavingToolBarBg = createAnimation(`ios-leaving-toolbar-${i}-bg-animation`);
|
||||
leavingToolBarBg.addElement(shadow(leavingToolBarEl).querySelector('.toolbar-background'));
|
||||
|
||||
const leavingBackButton = createAnimation(`ios-leaving-toolbar-${i}-back-button-animation`);
|
||||
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('ios-leaving-toolbar-${i}-back-button-text');
|
||||
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]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return rootAnimation as any;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
export const iosTransitionAnimation = (AnimationC: Animation, navEl: HTMLElement, opts: TransitionOptions): Promise<Animation> => {
|
||||
|
||||
const isRTL = (navEl.ownerDocument as any).dir === 'rtl';
|
||||
|
||||
@@ -1,10 +1,65 @@
|
||||
import { Animation } from '../../interface';
|
||||
import { Animation as AnimationNew, createAnimation } from '../animation/animation';
|
||||
import { TransitionOptions } from '../transition';
|
||||
|
||||
const TRANSLATEY = 'translateY';
|
||||
const OFF_BOTTOM = '40px';
|
||||
const CENTER = '0px';
|
||||
|
||||
export const newMdTransitionAnimation = (opts: TransitionOptions): AnimationNew => {
|
||||
try {
|
||||
const rootAnimation = createAnimation('md-root-animation');
|
||||
|
||||
const enteringEl = opts.enteringEl;
|
||||
const leavingEl = opts.leavingEl;
|
||||
const backDirection = (opts.direction === 'back');
|
||||
|
||||
const ionPageElement = getIonPageElement(enteringEl);
|
||||
const enteringToolbarEle = ionPageElement.querySelector('ion-toolbar');
|
||||
|
||||
rootAnimation
|
||||
.addElement(ionPageElement)
|
||||
.beforeRemoveClass('ion-page-invisible')
|
||||
.fill('both');
|
||||
|
||||
if (backDirection) {
|
||||
rootAnimation
|
||||
.duration(opts.duration || 200)
|
||||
.easing('cubic-bezier(0.47,0,0.745,0.715)');
|
||||
} else {
|
||||
rootAnimation
|
||||
.duration(opts.duration || 280)
|
||||
.easing('cubic-bezier(0.36,0.66,0.04,1)')
|
||||
.fromTo('transform', `translateY(${OFF_BOTTOM})`, `translateY(${CENTER})`)
|
||||
.fromTo('opacity', 0.01, 1);
|
||||
}
|
||||
|
||||
if (enteringToolbarEle) {
|
||||
const enteringToolBarAnimation = createAnimation('md-entering-toolbar-animation');
|
||||
enteringToolBarAnimation.addElement(enteringToolbarEle);
|
||||
rootAnimation.addAnimation(enteringToolBarAnimation);
|
||||
}
|
||||
|
||||
if (leavingEl && backDirection) {
|
||||
rootAnimation
|
||||
.duration(opts.duration || 200)
|
||||
.easing('cubic-bezier(0.47,0,0.745,0.715)');
|
||||
|
||||
const leavingPageAnimation = createAnimation('md-leaving-page-animation');
|
||||
leavingPageAnimation
|
||||
.addElement(getIonPageElement(leavingEl))
|
||||
.fromTo('transform', `translateY(${CENTER})`, `translateY(${OFF_BOTTOM})`)
|
||||
.fromTo('opacity', 1, 0);
|
||||
|
||||
rootAnimation.addAnimation(leavingPageAnimation);
|
||||
}
|
||||
|
||||
return rootAnimation;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
export const mdTransitionAnimation = (AnimationC: Animation, _: HTMLElement, opts: TransitionOptions): Promise<Animation> => {
|
||||
|
||||
const enteringEl = opts.enteringEl;
|
||||
|
||||
Reference in New Issue
Block a user