Skip to content

Commit e403a3a

Browse files
feat(animations): set smother animations for modal in ionic
1 parent 32201b7 commit e403a3a

24 files changed

Lines changed: 659 additions & 28 deletions

File tree

core/src/components/action-sheet/action-sheet.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type { AnimationBuilder, CssClassMap, FrameworkDelegate, OverlayInterface
2323
import type { OverlayEventDetail } from '../../utils/overlays-interface';
2424

2525
import type { ActionSheetButton } from './action-sheet-interface';
26+
import { ionicEnterAnimation } from './animations/ionic.enter';
2627
import { iosEnterAnimation } from './animations/ios.enter';
2728
import { iosLeaveAnimation } from './animations/ios.leave';
2829
import { mdEnterAnimation } from './animations/md.enter';
@@ -228,7 +229,7 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
228229

229230
await this.delegateController.attachViewToDom();
230231

231-
await present(this, 'actionSheetEnter', iosEnterAnimation, mdEnterAnimation);
232+
await present(this, 'actionSheetEnter', iosEnterAnimation, mdEnterAnimation, ionicEnterAnimation);
232233

233234
unlock();
234235
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { createAnimation } from '@utils/animation/animation';
2+
3+
import type { Animation } from '../../../interface';
4+
5+
/**
6+
* MD Action Sheet Enter Animation
7+
*/
8+
export const ionicEnterAnimation = (baseEl: HTMLElement): Animation => {
9+
const baseAnimation = createAnimation();
10+
const backdropAnimation = createAnimation();
11+
const wrapperAnimation = createAnimation();
12+
13+
backdropAnimation
14+
.addElement(baseEl.querySelector('ion-backdrop')!)
15+
.fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
16+
.beforeStyles({
17+
'pointer-events': 'none',
18+
})
19+
.afterClearStyles(['pointer-events']);
20+
21+
wrapperAnimation
22+
.addElement(baseEl.querySelector('.action-sheet-wrapper')!)
23+
.fromTo('transform', 'translateY(100%)', 'translateY(0%)');
24+
25+
return baseAnimation
26+
.addElement(baseEl)
27+
.easing('cubic-bezier(.36,.66,.04,1)')
28+
.duration(400)
29+
.addAnimation([backdropAnimation, wrapperAnimation]);
30+
};
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { createAnimation } from '@utils/animation/animation';
2+
3+
import type { Animation } from '../../../interface';
4+
5+
/**
6+
* MD Action Sheet Leave Animation
7+
*/
8+
export const ionicLeaveAnimation = (baseEl: HTMLElement): Animation => {
9+
const baseAnimation = createAnimation();
10+
const backdropAnimation = createAnimation();
11+
const wrapperAnimation = createAnimation();
12+
13+
backdropAnimation.addElement(baseEl.querySelector('ion-backdrop')!).fromTo('opacity', 'var(--backdrop-opacity)', 0);
14+
15+
wrapperAnimation
16+
.addElement(baseEl.querySelector('.action-sheet-wrapper')!)
17+
.fromTo('transform', 'translateY(0%)', 'translateY(100%)');
18+
19+
return baseAnimation
20+
.addElement(baseEl)
21+
.easing('cubic-bezier(.36,.66,.04,1)')
22+
.duration(450)
23+
.addAnimation([backdropAnimation, wrapperAnimation]);
24+
};

core/src/components/alert/alert.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import type { OverlayEventDetail } from '../../utils/overlays-interface';
2828
import type { IonicSafeString } from '../../utils/sanitization';
2929

3030
import type { AlertButton, AlertInput } from './alert-interface';
31+
import { ionicEnterAnimation } from './animations/ionic.enter';
3132
import { iosEnterAnimation } from './animations/ios.enter';
3233
import { iosLeaveAnimation } from './animations/ios.leave';
3334
import { mdEnterAnimation } from './animations/md.enter';
@@ -415,7 +416,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
415416

416417
await this.delegateController.attachViewToDom();
417418

418-
await present(this, 'alertEnter', iosEnterAnimation, mdEnterAnimation).then(() => {
419+
await present(this, 'alertEnter', iosEnterAnimation, mdEnterAnimation, ionicEnterAnimation).then(() => {
419420
/**
420421
* Check if alert has only one button and no inputs.
421422
* If so, then focus on the button. Otherwise, focus the alert wrapper.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { createAnimation } from '@utils/animation/animation';
2+
3+
import type { Animation } from '../../../interface';
4+
5+
/**
6+
* Ionic Alert Enter Animation
7+
*/
8+
export const ionicEnterAnimation = (baseEl: HTMLElement): Animation => {
9+
const baseAnimation = createAnimation();
10+
const backdropAnimation = createAnimation();
11+
const wrapperAnimation = createAnimation();
12+
13+
backdropAnimation
14+
.addElement(baseEl.querySelector('ion-backdrop')!)
15+
.fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
16+
.beforeStyles({
17+
'pointer-events': 'none',
18+
})
19+
.afterClearStyles(['pointer-events']);
20+
21+
wrapperAnimation.addElement(baseEl.querySelector('.alert-wrapper')!).keyframes([
22+
{ offset: 0, opacity: '0.01', transform: 'scale(0.9)' },
23+
{ offset: 1, opacity: '1', transform: 'scale(1)' },
24+
]);
25+
26+
return baseAnimation
27+
.addElement(baseEl)
28+
.easing('ease-in-out')
29+
.duration(150)
30+
.addAnimation([backdropAnimation, wrapperAnimation]);
31+
};
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { createAnimation } from '@utils/animation/animation';
2+
3+
import type { Animation } from '../../../interface';
4+
5+
/**
6+
* Md Alert Leave Animation
7+
*/
8+
export const ionicLeaveAnimation = (baseEl: HTMLElement): Animation => {
9+
const baseAnimation = createAnimation();
10+
const backdropAnimation = createAnimation();
11+
const wrapperAnimation = createAnimation();
12+
13+
backdropAnimation.addElement(baseEl.querySelector('ion-backdrop')!).fromTo('opacity', 'var(--backdrop-opacity)', 0);
14+
15+
wrapperAnimation.addElement(baseEl.querySelector('.alert-wrapper')!).fromTo('opacity', 0.99, 0);
16+
17+
return baseAnimation
18+
.addElement(baseEl)
19+
.easing('ease-in-out')
20+
.duration(150)
21+
.addAnimation([backdropAnimation, wrapperAnimation]);
22+
};
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { createAnimation } from '@utils/animation/animation';
2+
3+
import type { Animation } from '../../../interface';
4+
5+
/**
6+
* Ionic Loading Enter Animation
7+
*/
8+
export const ionicEnterAnimation = (baseEl: HTMLElement): Animation => {
9+
const baseAnimation = createAnimation();
10+
const backdropAnimation = createAnimation();
11+
const wrapperAnimation = createAnimation();
12+
13+
backdropAnimation
14+
.addElement(baseEl.querySelector('ion-backdrop')!)
15+
.fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
16+
.beforeStyles({
17+
'pointer-events': 'none',
18+
})
19+
.afterClearStyles(['pointer-events']);
20+
21+
wrapperAnimation.addElement(baseEl.querySelector('.loading-wrapper')!).keyframes([
22+
{ offset: 0, opacity: 0.01, transform: 'scale(1.1)' },
23+
{ offset: 1, opacity: 1, transform: 'scale(1)' },
24+
]);
25+
26+
return baseAnimation
27+
.addElement(baseEl)
28+
.easing('ease-in-out')
29+
.duration(200)
30+
.addAnimation([backdropAnimation, wrapperAnimation]);
31+
};
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { createAnimation } from '@utils/animation/animation';
2+
3+
import type { Animation } from '../../../interface';
4+
5+
/**
6+
* Md Loading Leave Animation
7+
*/
8+
export const ionicLeaveAnimation = (baseEl: HTMLElement): Animation => {
9+
const baseAnimation = createAnimation();
10+
const backdropAnimation = createAnimation();
11+
const wrapperAnimation = createAnimation();
12+
13+
backdropAnimation.addElement(baseEl.querySelector('ion-backdrop')!).fromTo('opacity', 'var(--backdrop-opacity)', 0);
14+
15+
wrapperAnimation.addElement(baseEl.querySelector('.loading-wrapper')!).keyframes([
16+
{ offset: 0, opacity: 0.99, transform: 'scale(1)' },
17+
{ offset: 1, opacity: 0, transform: 'scale(0.9)' },
18+
]);
19+
20+
return baseAnimation
21+
.addElement(baseEl)
22+
.easing('ease-in-out')
23+
.duration(200)
24+
.addAnimation([backdropAnimation, wrapperAnimation]);
25+
};

core/src/components/loading/loading.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type { OverlayEventDetail } from '../../utils/overlays-interface';
2323
import type { IonicSafeString } from '../../utils/sanitization';
2424
import type { SpinnerTypes } from '../spinner/spinner-configs';
2525

26+
import { ionicEnterAnimation } from './animations/ionic.enter';
2627
import { iosEnterAnimation } from './animations/ios.enter';
2728
import { iosLeaveAnimation } from './animations/ios.leave';
2829
import { mdEnterAnimation } from './animations/md.enter';
@@ -255,7 +256,7 @@ export class Loading implements ComponentInterface, OverlayInterface {
255256

256257
await this.delegateController.attachViewToDom();
257258

258-
await present(this, 'loadingEnter', iosEnterAnimation, mdEnterAnimation);
259+
await present(this, 'loadingEnter', iosEnterAnimation, mdEnterAnimation, ionicEnterAnimation);
259260

260261
if (this.duration > 0) {
261262
this.durationTimeout = setTimeout(() => this.dismiss(), this.duration + 10);
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { createAnimation } from '@utils/animation/animation';
2+
import { getElementRoot } from '@utils/helpers';
3+
4+
import type { Animation } from '../../../interface';
5+
import type { ModalAnimationOptions } from '../modal-interface';
6+
7+
import { createSheetEnterAnimation } from './sheet';
8+
9+
const createEnterAnimation = () => {
10+
const backdropAnimation = createAnimation()
11+
.fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
12+
.beforeStyles({
13+
'pointer-events': 'none',
14+
})
15+
.afterClearStyles(['pointer-events']);
16+
17+
const wrapperAnimation = createAnimation().keyframes([
18+
{ offset: 0, opacity: 0.01, transform: 'translateY(40px)' },
19+
{ offset: 1, opacity: 1, transform: `translateY(0px)` },
20+
]);
21+
22+
return { backdropAnimation, wrapperAnimation, contentAnimation: undefined };
23+
};
24+
25+
/**
26+
* Ionic Modal Enter Animation
27+
*/
28+
export const ionicEnterAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptions): Animation => {
29+
const { currentBreakpoint, expandToScroll } = opts;
30+
const root = getElementRoot(baseEl);
31+
const { wrapperAnimation, backdropAnimation, contentAnimation } =
32+
currentBreakpoint !== undefined ? createSheetEnterAnimation(opts) : createEnterAnimation();
33+
34+
backdropAnimation.addElement(root.querySelector('ion-backdrop')!);
35+
36+
wrapperAnimation.addElement(root.querySelector('.modal-wrapper')!);
37+
38+
// The content animation is only added if scrolling is enabled for
39+
// all the breakpoints.
40+
!expandToScroll && contentAnimation?.addElement(baseEl.querySelector('.ion-page')!);
41+
42+
backdropAnimation.duration(300).easing('ease-out');
43+
wrapperAnimation.duration(400).easing('cubic-bezier(0.32, 0.68, 0, 1)');
44+
contentAnimation?.duration(400).easing('cubic-bezier(0.32, 0.68, 0, 1)');
45+
46+
const baseAnimation = createAnimation().addElement(baseEl).addAnimation([backdropAnimation, wrapperAnimation]);
47+
48+
if (contentAnimation) {
49+
baseAnimation.addAnimation(contentAnimation);
50+
}
51+
52+
return baseAnimation;
53+
};

0 commit comments

Comments
 (0)