mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-17 10:41:13 +08:00
fix(animation): avoid partial keyframes (#19169)
* ensure custom props not part of final keyframes * fix clear * clean up
This commit is contained in:
@ -16,8 +16,8 @@ export interface Animation {
|
|||||||
progressEnd(shouldComplete: boolean, step: number, dur: number | undefined): Animation;
|
progressEnd(shouldComplete: boolean, step: number, dur: number | undefined): Animation;
|
||||||
|
|
||||||
from(property: string, value: any): Animation;
|
from(property: string, value: any): Animation;
|
||||||
to(property: string, value: any, clearAfterAnimation?: boolean): Animation;
|
to(property: string, value: any): Animation;
|
||||||
fromTo(property: string, fromValue: any, toValue: any, clearAfterAnimation?: boolean): Animation;
|
fromTo(property: string, fromValue: any, toValue: any): Animation;
|
||||||
keyframes(keyframes: any[]): Animation;
|
keyframes(keyframes: any[]): Animation;
|
||||||
|
|
||||||
addAnimation(animationToADd: Animation | Animation[] | undefined | null): Animation;
|
addAnimation(animationToADd: Animation | Animation[] | undefined | null): Animation;
|
||||||
|
@ -43,7 +43,7 @@ export const generateKeyframeRules = (keyframes: any[] = []) => {
|
|||||||
|
|
||||||
const frameString = [];
|
const frameString = [];
|
||||||
for (const property in keyframe) {
|
for (const property in keyframe) {
|
||||||
if (keyframe.hasOwnProperty(property) && property !== 'offset' && property !== 'clear') {
|
if (keyframe.hasOwnProperty(property) && property !== 'offset') {
|
||||||
frameString.push(`${property}: ${keyframe[property]};`);
|
frameString.push(`${property}: ${keyframe[property]};`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,8 @@ export const createAnimation = () => {
|
|||||||
|
|
||||||
elements.length = 0;
|
elements.length = 0;
|
||||||
childAnimations.length = 0;
|
childAnimations.length = 0;
|
||||||
|
_keyframes.length = 0;
|
||||||
|
|
||||||
clearOnFinish();
|
clearOnFinish();
|
||||||
|
|
||||||
initialized = false;
|
initialized = false;
|
||||||
@ -1033,13 +1035,8 @@ export const createAnimation = () => {
|
|||||||
if (firstFrame != null && (firstFrame.offset === undefined || firstFrame.offset === 0)) {
|
if (firstFrame != null && (firstFrame.offset === undefined || firstFrame.offset === 0)) {
|
||||||
firstFrame[property] = value;
|
firstFrame[property] = value;
|
||||||
} else {
|
} else {
|
||||||
const object: any = {
|
|
||||||
offset: 0
|
|
||||||
};
|
|
||||||
object[property] = value;
|
|
||||||
|
|
||||||
_keyframes = [
|
_keyframes = [
|
||||||
object,
|
{ offset: 0, [property]: value },
|
||||||
..._keyframes
|
..._keyframes
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -1047,84 +1044,22 @@ export const createAnimation = () => {
|
|||||||
return ani;
|
return ani;
|
||||||
};
|
};
|
||||||
|
|
||||||
const to = (property: string, value: any, clearAfterAnimation?: boolean) => {
|
const to = (property: string, value: any) => {
|
||||||
const lastFrame = _keyframes[_keyframes.length - 1];
|
const lastFrame = _keyframes[_keyframes.length - 1];
|
||||||
|
|
||||||
if (lastFrame != null && (lastFrame.offset === undefined || lastFrame.offset === 1)) {
|
if (lastFrame != null && (lastFrame.offset === undefined || lastFrame.offset === 1)) {
|
||||||
|
lastFrame[property] = value;
|
||||||
/**
|
|
||||||
* If last frame is not the clear frame
|
|
||||||
* set the value as usual
|
|
||||||
*/
|
|
||||||
if (!lastFrame.clear) {
|
|
||||||
lastFrame[property] = value;
|
|
||||||
} else {
|
|
||||||
/**
|
|
||||||
* If last frame is the keyframe then we need to
|
|
||||||
* set the value on the previous frame
|
|
||||||
*/
|
|
||||||
const secondToLastFrame = _keyframes[_keyframes.length - 2];
|
|
||||||
secondToLastFrame[property] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If not on the last possible keyframe, add
|
|
||||||
* the last keyframe
|
|
||||||
*/
|
|
||||||
} else {
|
} else {
|
||||||
_keyframes = [
|
_keyframes = [
|
||||||
..._keyframes,
|
..._keyframes,
|
||||||
{ offset: 1, [property]: value }
|
{ offset: 1, [property]: value }
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clearAfterAnimation) {
|
|
||||||
const lastFrame = _keyframes[_keyframes.length - 1];
|
|
||||||
if (lastFrame != null) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If we are on the clear frame, just set the property
|
|
||||||
*/
|
|
||||||
if (lastFrame.clear) {
|
|
||||||
lastFrame[property] = '';
|
|
||||||
return ani;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If we are already setup for a clear frame, just mark it
|
|
||||||
* as such and set the property
|
|
||||||
*/
|
|
||||||
const secondToLastFrame = _keyframes[_keyframes.length - 2];
|
|
||||||
if (lastFrame.offset === 1 && secondToLastFrame.offset === 0.99) {
|
|
||||||
lastFrame.clear = true;
|
|
||||||
lastFrame[property] = '';
|
|
||||||
secondToLastFrame[property] = value;
|
|
||||||
return ani;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the last frame is not the clear frame
|
|
||||||
* and has an offset of 1, we need to move it
|
|
||||||
* back by a frame to account for the clear frame
|
|
||||||
*/
|
|
||||||
if (lastFrame.offset === 1 && secondToLastFrame.offset !== 0.99) {
|
|
||||||
lastFrame.offset = 0.99;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a clear frame that runs immediately after
|
|
||||||
* the last frame that the user has set. This will
|
|
||||||
* allow users to clear certain properties from elements
|
|
||||||
*/
|
|
||||||
_keyframes.push({ offset: lastFrame.offset + 0.01, [property]: '', clear: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ani;
|
return ani;
|
||||||
};
|
};
|
||||||
|
|
||||||
const fromTo = (property: string, fromValue: any, toValue: any, clearAfterAnimation?: boolean) => {
|
const fromTo = (property: string, fromValue: any, toValue: any) => {
|
||||||
return from(property, fromValue).to(property, toValue, clearAfterAnimation);
|
return from(property, fromValue).to(property, toValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
return ani = {
|
return ani = {
|
||||||
|
@ -114,37 +114,6 @@ describe('Animation Class', () => {
|
|||||||
offset: 1
|
offset: 1
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should clear properties at the end of an animation', () => {
|
|
||||||
animation
|
|
||||||
.fromTo('opacity', 0, 1, true)
|
|
||||||
.fromTo('background', 'red', 'blue')
|
|
||||||
.fromTo('color', 'purple', 'green', true);
|
|
||||||
|
|
||||||
const keyframes = animation.getKeyframes();
|
|
||||||
|
|
||||||
expect(keyframes.length).toEqual(3);
|
|
||||||
expect(keyframes[0]).toEqual({
|
|
||||||
opacity: 0,
|
|
||||||
color: 'purple',
|
|
||||||
background: 'red',
|
|
||||||
offset: 0
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(keyframes[1]).toEqual({
|
|
||||||
opacity: 1,
|
|
||||||
color: 'green',
|
|
||||||
offset: 0.99,
|
|
||||||
background: 'blue'
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(keyframes[2]).toEqual({
|
|
||||||
clear: true,
|
|
||||||
opacity: '',
|
|
||||||
color: '',
|
|
||||||
offset: 1
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should mix keyframes and fromTo properly', () => {
|
it('should mix keyframes and fromTo properly', () => {
|
||||||
animation
|
animation
|
||||||
@ -153,7 +122,7 @@ describe('Animation Class', () => {
|
|||||||
{ offset: 0.99, background: 'blue' },
|
{ offset: 0.99, background: 'blue' },
|
||||||
{ offset: 1, background: 'green' }
|
{ offset: 1, background: 'green' }
|
||||||
])
|
])
|
||||||
.fromTo('opacity', 0, 1, true)
|
.fromTo('opacity', 0, 1)
|
||||||
|
|
||||||
const keyframes = animation.getKeyframes();
|
const keyframes = animation.getKeyframes();
|
||||||
expect(keyframes.length).toEqual(3);
|
expect(keyframes.length).toEqual(3);
|
||||||
@ -164,16 +133,14 @@ describe('Animation Class', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(keyframes[1]).toEqual({
|
expect(keyframes[1]).toEqual({
|
||||||
opacity: 1,
|
|
||||||
background: 'blue',
|
background: 'blue',
|
||||||
offset: 0.99
|
offset: 0.99
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(keyframes[2]).toEqual({
|
expect(keyframes[2]).toEqual({
|
||||||
opacity: '',
|
opacity: 1,
|
||||||
background: 'green',
|
background: 'green',
|
||||||
offset: 1,
|
offset: 1
|
||||||
clear: true
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -138,7 +138,12 @@ export const iosTransitionAnimation = (navEl: HTMLElement, opts: TransitionOptio
|
|||||||
|
|
||||||
enteringToolBarBg
|
enteringToolBarBg
|
||||||
.beforeClearStyles([OPACITY])
|
.beforeClearStyles([OPACITY])
|
||||||
.fromTo(OPACITY, 0.01, 1, true);
|
.keyframes([
|
||||||
|
{ offset: 0, opacity: 0.01 },
|
||||||
|
{ offset: 0.99, opacity: 1 },
|
||||||
|
{ offset: 1, opacity: 'var(--opacity)' }
|
||||||
|
// TODO: Find a way to support clearing properties from Web Animations
|
||||||
|
]);
|
||||||
|
|
||||||
// forward direction, entering page has a back button
|
// forward direction, entering page has a back button
|
||||||
enteringBackButton.fromTo(OPACITY, 0.01, 1);
|
enteringBackButton.fromTo(OPACITY, 0.01, 1);
|
||||||
|
Reference in New Issue
Block a user