user1995781
user1995781

Reputation: 19453

Ionic custom modal animation

Ionic modal comes with the standard animation of slide-in-up. Is it possible that we can change the animation to fade-in?

Upvotes: 5

Views: 13195

Answers (6)

francescodipietr0
francescodipietr0

Reputation: 11

Few days ago I got the same issue on Angular 2. I solved it like that:

I have two different components:

  • The navbar component (or whatever you use to click and make the ion-modal appears); in my case it will be "nav-header.component.ts"
  • The modal component, in my case "modal-profile.component.ts"

modal-profile.component.ts (ModalProfileComponent class):

Here, you need to import "ModalController" and then you need to pass its own istance to ModalProfileComponent constructor.

import { ModalController } from '@ionic/angular';

export class ModalProfileComponent {
  constructor( public modal: ModalController ) { }
}

Also, I used this class to structurally and graphically edit the modal (HTML and CSS).

nav-header.component.ts (NavHeaderComponent class):

First of all, import AnimationController, ModalController and the modal class you created at the first step:

import { AnimationController } from '@ionic/angular';
import { ModalController } from '@ionic/angular';
import { ModalProfileComponent } from '../../home/components/modal-profile.component';

Then, add as constructor argument the following instances:

constructor(private modal: ModalController, private animationCtrl: AnimationController) {}

Now, declare a method you will call by clicking on the HTML element you want. I will use onOptionsClick().

In this method, you can now use the ion-modal function enterAnimation and leaveAnimation to customize the modal behaviour when it opens and closes.

You can follow the ionic documentation, section "Modals" at this link.

Following, the solution I improved:

async onOptionsClick() {

const enterAnimation = (baseEl: HTMLElement) => {
  const root = baseEl.shadowRoot;

  const backdropAnimation = this.animationCtrl.create()
    .addElement(root.querySelector('ion-backdrop')!)
    .fromTo('opacity', '0.01', 'var(--backdrop-opacity)');

  const wrapperAnimation = this.animationCtrl.create()
    .addElement(root.querySelector('.modal-wrapper')!)
    .fromTo('transform', 'translateY(-100%)', 'translateY(0)');

  return this.animationCtrl.create()
    .addElement(baseEl)
    .easing('ease-out')
    .duration(500)
    .addAnimation([backdropAnimation, wrapperAnimation]);
}

const leaveAnimation = (baseEl: HTMLElement) => {
  return enterAnimation(baseEl).direction('reverse');
}


const modal = await this.modal.create({
  component: ModalProfileComponent,
  enterAnimation,
  leaveAnimation
});
modal.present();

}

Hope you find usefull! :)

Upvotes: 1

Volkan Talayhan
Volkan Talayhan

Reputation: 36

 import { AnimationController } from '@ionic/angular';
export const SwipeToCloseDefaults = {
    MIN_PRESENTING_SCALE: 0.93,
  };
export const enterFromRightAnimation = (baseEl, presentingEl) => {
    const backdropAnimation = new AnimationController().create()
      .addElement(baseEl.querySelector('ion-backdrop'))
      .fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
      .beforeStyles({
      'pointer-events': 'none'
    })
      .afterClearStyles(['pointer-events']);
    const wrapperAnimation = new AnimationController().create()
      .addElement(baseEl.querySelectorAll('.modal-wrapper, .modal-shadow'))
      .beforeStyles({ 'opacity': 1 })
      .fromTo('transform', 'translateX(100vh)', 'translateX(0vh)');
    const baseAnimation = new AnimationController().create()
      .addElement(baseEl)
      .easing('cubic-bezier(0.32,0.72,0,1)')
      .duration(500)
      .addAnimation(wrapperAnimation);
    if (presentingEl) {
      const isMobile = window.innerWidth < 768;
      const hasCardModal = (presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined);
      const presentingAnimation = new AnimationController().create()
        .beforeStyles({
        'transform': 'translateX(0)',
        'transform-origin': 'top center',
        'overflow': 'hidden'
      });
      const bodyEl = document.body;
      if (isMobile) {
        /**
         * Fallback for browsers that does not support `max()` (ex: Firefox)
         * No need to worry about statusbar padding since engines like Gecko
         * are not used as the engine for standlone Cordova/Capacitor apps
         */
        const transformOffset = (!CSS.supports('width', 'max(0px, 1px)')) ? '30px' : 'max(30px, var(--ion-safe-area-top))';
        const modalTransform = hasCardModal ? '-10px' : transformOffset;
        const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
        const finalTransform = `translateX(${modalTransform}) scale(${toPresentingScale})`;
        presentingAnimation
          .afterStyles({
          'transform': finalTransform
        })
          .beforeAddWrite(() => bodyEl.style.setProperty('background-color', 'black'))
          .addElement(presentingEl)
          .keyframes([
          { offset: 0, filter: 'contrast(1)', transform: 'translateX(0px) scale(1)', borderRadius: '0px' },
          { offset: 1, filter: 'contrast(0.85)', transform: finalTransform, borderRadius: '10px 10px 0 0' }
        ]);
        baseAnimation.addAnimation(presentingAnimation);
      }
      else {
        baseAnimation.addAnimation(backdropAnimation);
        if (!hasCardModal) {
          wrapperAnimation.fromTo('opacity', '0', '1');
        }
        else {
          const toPresentingScale = (hasCardModal) ? SwipeToCloseDefaults.MIN_PRESENTING_SCALE : 1;
          const finalTransform = `translateX(-10px) scale(${toPresentingScale})`;
          presentingAnimation
            .afterStyles({
            'transform': finalTransform
          })
            .addElement(presentingEl.querySelector('.modal-wrapper'))
            .keyframes([
            { offset: 0, filter: 'contrast(1)', transform: 'translateX(0) scale(1)' },
            { offset: 1, filter: 'contrast(0.85)', transform: finalTransform }
          ]);
          const shadowAnimation =new AnimationController().create()
            .afterStyles({
            'transform': finalTransform
          })
            .addElement(presentingEl.querySelector('.modal-shadow'))
            .keyframes([
            { offset: 0, opacity: '1', transform: 'translateX(0) scale(1)' },
            { offset: 1, opacity: '0', transform: finalTransform }
          ]);
          baseAnimation.addAnimation([presentingAnimation, shadowAnimation]);
        }
      }
    }
    else {
      baseAnimation.addAnimation(backdropAnimation);
    }
    return baseAnimation;
  };
  

export const leaveToRightAnimation =  (baseEl, presentingEl, duration = 500) => {
    const backdropAnimation = new AnimationController().create()
      .addElement(baseEl.querySelector('ion-backdrop'))
      .fromTo('opacity', 'var(--backdrop-opacity)', 0.0);
    const wrapperAnimation = new AnimationController().create()
      .addElement(baseEl.querySelectorAll('.modal-wrapper, .modal-shadow'))
      .beforeStyles({ 'opacity': 1 })
      .fromTo('transform', 'translateX(0vh)', 'translateX(100vh)');
    const baseAnimation = new AnimationController().create()
      .addElement(baseEl)
      .easing('cubic-bezier(0.32,0.72,0,1)')
      .duration(duration)
      .addAnimation(wrapperAnimation);
    if (presentingEl) {
      const isMobile = window.innerWidth < 768;
      const hasCardModal = (presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined);
      const presentingAnimation = new AnimationController().create()
        .beforeClearStyles(['transform'])
        .afterClearStyles(['transform'])
        .onFinish(currentStep => {
        // only reset background color if this is the last card-style modal
        if (currentStep !== 1) {
          return;
        }
        presentingEl.style.setProperty('overflow', '');
        const numModals = Array.from(bodyEl.querySelectorAll('ion-modal')).filter(m => m.presentingElement !== undefined).length;
        if (numModals <= 1) {
          bodyEl.style.setProperty('background-color', '');
        }
      });
      const bodyEl = document.body;
      if (isMobile) {
        const transformOffset = (!CSS.supports('width', 'max(0px, 1px)')) ? '30px' : 'max(30px, var(--ion-safe-area-top))';
        const modalTransform = hasCardModal ? '-10px' : transformOffset;
        const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
        const finalTransform = `translateX(${modalTransform}) scale(${toPresentingScale})`;
        presentingAnimation
          .addElement(presentingEl)
          .keyframes([
          { offset: 0, filter: 'contrast(0.85)', transform: finalTransform, borderRadius: '10px 10px 0 0' },
          { offset: 1, filter: 'contrast(1)', transform: 'translateX(0px) scale(1)', borderRadius: '0px' }
        ]);
        baseAnimation.addAnimation(presentingAnimation);
      }
      else {
        baseAnimation.addAnimation(backdropAnimation);
        if (!hasCardModal) {
          wrapperAnimation.fromTo('opacity', '1', '0');
        }
        else {
          const toPresentingScale = (hasCardModal) ? SwipeToCloseDefaults.MIN_PRESENTING_SCALE : 1;
          const finalTransform = `translateX(-10px) scale(${toPresentingScale})`;
          presentingAnimation
            .addElement(presentingEl.querySelector('.modal-wrapper'))
            .afterStyles({
            'transform': 'translate3d(0, 0, 0)'
          })
            .keyframes([
            { offset: 0, filter: 'contrast(0.85)', transform: finalTransform },
            { offset: 1, filter: 'contrast(1)', transform: 'translateX(0) scale(1)' }
          ]);
          const shadowAnimation = new AnimationController().create()
            .addElement(presentingEl.querySelector('.modal-shadow'))
            .afterStyles({
            'transform': 'translateX(0) scale(1)'
          })
            .keyframes([
            { offset: 0, opacity: '0', transform: finalTransform },
            { offset: 1, opacity: '1', transform: 'translateX(0) scale(1)' }
          ]);
          baseAnimation.addAnimation([presentingAnimation, shadowAnimation]);
        }
      }
    }
    else {
      baseAnimation.addAnimation(backdropAnimation);
    }
    return baseAnimation;
  };
    
  1. Create a new filename.ts copy and paste this... then only import this file to component that you will create the modal. import { leaveToRightAnimation,enterFromRightAnimation} from 'filename'; no needs to import it into module
  2. Then set modal options enterAnimation: enterFromRightAnimation, leaveAnimation: leaveToRightAnimation

Upvotes: 0

Mohamed Saleh
Mohamed Saleh

Reputation: 3287

You could use the ready animation from this opensource:

Modal / Popover transition class for Ionic v3 here

Upvotes: 0

Shreejibawa
Shreejibawa

Reputation: 1868

In order to add custom transitions for Ionic Modal We will be using Ionic Modal Options enterAnimation and leaveAnimationfrom ModalOptions interface. For a modal there are transition states: On Enter of modal and and On Leave of modal when we close it. If you look at the Ionic Modal options interface you will find 2 options to add animations for both the states.

export interface ModalOptions {
    showBackdrop?: boolean;
    enableBackdropDismiss?: boolean;
    enterAnimation?: string;
    leaveAnimation?: string;
    cssClass?: string;
}

We will use these options in modal to specify transition class we create using Animation class from ionic-angular. So lets see how we can create and custom animations step by step.

Create 2 transition classes for enter and leave:

on-enter-translate.transition.ts

import { Animation, PageTransition } from 'ionic-angular';
export class ModalTranslateEnterTransition extends PageTransition {
    public init() {
        const ele = this.enteringView.pageRef().nativeElement;
        const wrapper = new Animation(this.plt, ele.querySelector('.modal-wrapper'));
        wrapper.beforeStyles({ 'transform': 'translateX(100%);', 'opacity': 1 });
        wrapper.fromTo('transform', 'translateX(100%)', 'translateX(0)');
        wrapper.fromTo('opacity', 1, 1);
        this
            .element(this.enteringView.pageRef())
            .duration(500)
            .easing('cubic-bezier(.1, .7, .1, 1)')
            .add(wrapper);
    }
}

on-leave-translate.transition.ts

import { Animation, PageTransition } from 'ionic-angular';

export class ModalTranslateLeaveTransition extends PageTransition {

    public init() {
        const ele = this.leavingView.pageRef().nativeElement;
        const wrapper = new Animation(this.plt, ele.querySelector('.modal-wrapper'));
        const contentWrapper = new Animation(this.plt, ele.querySelector('.wrapper'));

        wrapper.beforeStyles({ 'transform': 'translateX(100%)', 'opacity': 1 });
        wrapper.fromTo('transform', 'translateX(0)', 'translateX(100%)');
        wrapper.fromTo('opacity', 1, 1);
        contentWrapper.fromTo('opacity', 1, 0);

        this
            .element(this.leavingView.pageRef())
            .duration(500)
            .easing('cubic-bezier(.1, .7, .1, 1)')
            .add(contentWrapper)
            .add(wrapper);
    }
}

Then import those modules in app.module.ts

export class AppModule {
    constructor(public config: Config) {
        this.setCustomTransitions();
    }
    private setCustomTransitions() {
        this.config.setTransition('modal-translate-up-enter', ModalTranslateEnterTransition);
        this.config.setTransition('modal-translate-up-leave', ModalTranslateLeaveTransition);
    }
}

And create modal using following options:

var modal = this.modalCtrl.create(AddToCartModalPage, {
    productId: this.productId,
    skuId: this.skuId,
    zipcode: this.zipcode,
    sellerProfileId: this.sellerProfileId,
    branchId: this.branchId,
    changeSeller: this.changeSeller
}, {
    showBackdrop: false,
    enableBackdropDismiss: false,
    cssClass: 'add-to-cart-modal',
    enterAnimation: 'modal-translate-up-enter',
    leaveAnimation: 'modal-translate-up-leave'
});

Find more information my article here: Blog

Find complete demo repository here: Github

Upvotes: 6

Luan Kevin Ferreira
Luan Kevin Ferreira

Reputation: 1380

You can add your own animation css, ex:

.slide-in-right {
  -webkit-transform: translateX(100%);
    transform: translateX(100%); }

.slide-in-right.ng-enter, .slide-in-right > .ng-enter {
  -webkit-transition: all cubic-bezier(0.1, 0.7, 0.1, 1) 400ms;
  transition: all cubic-bezier(0.1, 0.7, 0.1, 1) 400ms; }

.slide-in-right.ng-enter-active, .slide-in-right > .ng-enter-active {
  -webkit-transform: translateX(0);
    transform: translateX(0); }

.slide-in-right.ng-leave, .slide-in-right > .ng-leave {
  -webkit-transition: all ease-in-out 250ms;
  transition: all ease-in-out 250ms; }

and usage with 'slide-in-right'

the same for 'fade-in'

https://forum.ionicframework.com/t/slide-in-right-animation-for-ionicmodal/18882

Upvotes: 3

gandharv garg
gandharv garg

Reputation: 2171

no ionic does not provide fade-in animation. But you can do that by using animate.css

Upvotes: 0

Related Questions