Ionic-framework: Backdrop opacity customization

Created on 10 Nov 2016  路  11Comments  路  Source: ionic-team/ionic-framework

Short description of the problem:

The current backdrop implementation, on ionic 2, doesn't let us customize the backdrop opacity. It's hardcoded.

What behavior are you expecting?

It should allow the option to change the opacity from and to values

Which Ionic Version? 1.x or 2.x
Ionic 2 - 2.0.0-rc.2

v3

Most helpful comment

I created a custom popoverEnter transition after looking at the original code, you can see below I customized the opacity from the original 0.08 to 0.5: backdropAnimation.fromTo('opacity', 0.01, 0.5);

I have a helper folder, so I created a new ts file there: popover-enter.transition.ts

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

export class PopoverEnterTransition extends Transition {
  public init() {
    let originY = 'top';
    let originX = 'left';

    const baseEl= this.enteringView.pageRef().nativeElement;
    const contentEl = baseEl.querySelector('.popover-content') as HTMLElement;
    const contentDimentions = contentEl.getBoundingClientRect();
    const contentWidth = contentDimentions.width;
    const contentHeight = contentDimentions.height;

    const bodyWidth = window.innerWidth;
    const bodyHeight = window.innerHeight;

    // If ev was passed, use that for target element
    const targetDim =
    this.opts.ev && this.opts.ev.target && (this.opts.ev.target as HTMLElement).getBoundingClientRect();

    const targetTop =
      targetDim && 'top' in targetDim
        ? targetDim.top
        : bodyHeight / 2 - contentHeight / 2;
    const targetLeft =
      targetDim && 'left' in targetDim ? targetDim.left : bodyWidth / 2;
    const targetWidth = (targetDim && targetDim.width) || 0;
    const targetHeight = (targetDim && targetDim.height) || 0;

    const arrowEl = baseEl.querySelector('.popover-arrow') as HTMLElement;

    const arrowDim = arrowEl.getBoundingClientRect();
    const arrowWidth = arrowDim.width;
    const arrowHeight = arrowDim.height;

    if (!targetDim) {
      arrowEl.style.display = 'none';
    }

    const arrowCSS = {
      top: targetTop + targetHeight,
      left: targetLeft + targetWidth / 2 - arrowWidth / 2
    };

    const popoverCSS: { top: any; left: any } = {
      top: targetTop + targetHeight + (arrowHeight - 1),
      left: targetLeft + targetWidth / 2 - contentWidth / 2
    };

    // If the popover left is less than the padding it is off screen
    // to the left so adjust it, else if the width of the popover
    // exceeds the body width it is off screen to the right so adjust
    //
    let checkSafeAreaLeft = false;
    let checkSafeAreaRight = false;

    // If the popover left is less than the padding it is off screen
    // to the left so adjust it, else if the width of the popover
    // exceeds the body width it is off screen to the right so adjust
    // 25 is a random/arbitrary number. It seems to work fine for ios11
    // and iPhoneX. Is it perfect? No. Does it work? Yes.
    if (popoverCSS.left < POPOVER_IOS_BODY_PADDING + 25) {
      checkSafeAreaLeft = true;
      popoverCSS.left = POPOVER_IOS_BODY_PADDING;
    } else if (
      contentWidth + POPOVER_IOS_BODY_PADDING + popoverCSS.left + 25 >
      bodyWidth
    ) {
      // Ok, so we're on the right side of the screen,
      // but now we need to make sure we're still a bit further right
      // cus....notchurally... Again, 25 is random. It works tho
      checkSafeAreaRight = true;
      popoverCSS.left = bodyWidth - contentWidth - POPOVER_IOS_BODY_PADDING;
      originX = 'right';
    }

    // make it pop up if there's room above
    if (
      (targetTop + targetHeight + contentHeight) > bodyHeight &&
      (targetTop - contentHeight) > 0
    ) {
      arrowCSS.top = targetTop - (arrowHeight + 1);
      console.log(arrowCSS);
      console.log(targetTop);
      console.log(contentHeight);
      popoverCSS.top = targetTop - contentHeight - (arrowHeight - 1);

      baseEl.className = baseEl.className + ' popover-bottom';
      originY = 'bottom';
      // If there isn't room for it to pop up above the target cut it off
    } else if (targetTop + targetHeight + contentHeight > bodyHeight) {
      contentEl.style.bottom = POPOVER_IOS_BODY_PADDING + '%';
    }

    arrowEl.style.top = arrowCSS.top + 'px';
    arrowEl.style.left = arrowCSS.left + 'px';

    contentEl.style.top = popoverCSS.top + 'px';
    contentEl.style.left = popoverCSS.left + 'px';

    if (checkSafeAreaLeft) {
      if (CSS.supports('left', 'constant(safe-area-inset-left)')) {
        contentEl.style.left = `calc(${popoverCSS.left}px + constant(safe-area-inset-left)`;
      } else if (CSS.supports('left', 'env(safe-area-inset-left)')) {
        contentEl.style.left = `calc(${popoverCSS.left}px + env(safe-area-inset-left)`;
      }
    }

    if (checkSafeAreaRight) {
      if (CSS.supports('right', 'constant(safe-area-inset-right)')) {
        contentEl.style.left = `calc(${popoverCSS.left}px - constant(safe-area-inset-right)`;
      } else if (CSS.supports('right', 'env(safe-area-inset-right)')) {
        contentEl.style.left = `calc(${popoverCSS.left}px - env(safe-area-inset-right)`;
      }
    }

    contentEl.style.transformOrigin = originY + ' ' + originX;

    const backdropAnimation = new Animation(this.plt, baseEl.querySelector('ion-backdrop'));
    backdropAnimation.fromTo('opacity', 0.01, 0.5);

    const wrapperAnimation = new Animation(this.plt, baseEl.querySelector('.popover-wrapper'));
    wrapperAnimation.fromTo('opacity', 0.01, 1);

    this
      .element(this.enteringView.pageRef())
      .easing('ease')
      .duration(100)
      .add(backdropAnimation)
      .add(wrapperAnimation);
  }
}

const POPOVER_IOS_BODY_PADDING = 5;

For global popoverEnter config in app.module.ts add transition to IonicModule:

IonicModule.forRoot(MyApp, {
     popoverEnter: 'custom-popover-enter'
}),

In app.component.ts, import the new class PopoverEnterTransition and then add:

constructor(...private config: Config) {
    this.setCustomTransitions();
}
setCustomTransitions() {
    this.config.setTransition('custom-popover-enter', PopoverEnterTransition);
}

As you can see it's a lot of code, but this allows you to customize the opacity now without breaking the animation. Hope this helps!

All 11 comments

@NelsonBrandao :+1: for pointing this out, were searching for a way to change the Opacity for too long... Would be really nice, if ionic2 would give us that option. If I try via CSS, it destroys the fading-animation.

Hello everyone! Thanks for the feature request. I'm going to move this issue over to our internal list of feature requests for evaluation. We are continually prioritizing all requests that we receive with outstanding issues. We are extremely grateful for your feedback, but it may not always be our next priority. I'll copy the issue back to this repository when we have begun implementation. Thanks!

This issue was moved to driftyco/ionic-feature-requests#140

@jgw96 the link seems to be dead...

@jgw96 @brandyscarney still happening in 3.7.1... It would be nice to configure the opacity of the backdrop, mostly in apps with a dark background where the current opacity is hard to notice...

Why don't we just add a sass variable? We already have $backdrop-color

Currently it is possible to overload the property in this way for example in app.scss

.loading-md, .loading-ios{ ion-backdrop{ opacity: 0.7 !important; } }

It will change opacity but this will also"disable" animation while closing backdrop which looks like app would freeze for few ms :/

I created a custom popoverEnter transition after looking at the original code, you can see below I customized the opacity from the original 0.08 to 0.5: backdropAnimation.fromTo('opacity', 0.01, 0.5);

I have a helper folder, so I created a new ts file there: popover-enter.transition.ts

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

export class PopoverEnterTransition extends Transition {
  public init() {
    let originY = 'top';
    let originX = 'left';

    const baseEl= this.enteringView.pageRef().nativeElement;
    const contentEl = baseEl.querySelector('.popover-content') as HTMLElement;
    const contentDimentions = contentEl.getBoundingClientRect();
    const contentWidth = contentDimentions.width;
    const contentHeight = contentDimentions.height;

    const bodyWidth = window.innerWidth;
    const bodyHeight = window.innerHeight;

    // If ev was passed, use that for target element
    const targetDim =
    this.opts.ev && this.opts.ev.target && (this.opts.ev.target as HTMLElement).getBoundingClientRect();

    const targetTop =
      targetDim && 'top' in targetDim
        ? targetDim.top
        : bodyHeight / 2 - contentHeight / 2;
    const targetLeft =
      targetDim && 'left' in targetDim ? targetDim.left : bodyWidth / 2;
    const targetWidth = (targetDim && targetDim.width) || 0;
    const targetHeight = (targetDim && targetDim.height) || 0;

    const arrowEl = baseEl.querySelector('.popover-arrow') as HTMLElement;

    const arrowDim = arrowEl.getBoundingClientRect();
    const arrowWidth = arrowDim.width;
    const arrowHeight = arrowDim.height;

    if (!targetDim) {
      arrowEl.style.display = 'none';
    }

    const arrowCSS = {
      top: targetTop + targetHeight,
      left: targetLeft + targetWidth / 2 - arrowWidth / 2
    };

    const popoverCSS: { top: any; left: any } = {
      top: targetTop + targetHeight + (arrowHeight - 1),
      left: targetLeft + targetWidth / 2 - contentWidth / 2
    };

    // If the popover left is less than the padding it is off screen
    // to the left so adjust it, else if the width of the popover
    // exceeds the body width it is off screen to the right so adjust
    //
    let checkSafeAreaLeft = false;
    let checkSafeAreaRight = false;

    // If the popover left is less than the padding it is off screen
    // to the left so adjust it, else if the width of the popover
    // exceeds the body width it is off screen to the right so adjust
    // 25 is a random/arbitrary number. It seems to work fine for ios11
    // and iPhoneX. Is it perfect? No. Does it work? Yes.
    if (popoverCSS.left < POPOVER_IOS_BODY_PADDING + 25) {
      checkSafeAreaLeft = true;
      popoverCSS.left = POPOVER_IOS_BODY_PADDING;
    } else if (
      contentWidth + POPOVER_IOS_BODY_PADDING + popoverCSS.left + 25 >
      bodyWidth
    ) {
      // Ok, so we're on the right side of the screen,
      // but now we need to make sure we're still a bit further right
      // cus....notchurally... Again, 25 is random. It works tho
      checkSafeAreaRight = true;
      popoverCSS.left = bodyWidth - contentWidth - POPOVER_IOS_BODY_PADDING;
      originX = 'right';
    }

    // make it pop up if there's room above
    if (
      (targetTop + targetHeight + contentHeight) > bodyHeight &&
      (targetTop - contentHeight) > 0
    ) {
      arrowCSS.top = targetTop - (arrowHeight + 1);
      console.log(arrowCSS);
      console.log(targetTop);
      console.log(contentHeight);
      popoverCSS.top = targetTop - contentHeight - (arrowHeight - 1);

      baseEl.className = baseEl.className + ' popover-bottom';
      originY = 'bottom';
      // If there isn't room for it to pop up above the target cut it off
    } else if (targetTop + targetHeight + contentHeight > bodyHeight) {
      contentEl.style.bottom = POPOVER_IOS_BODY_PADDING + '%';
    }

    arrowEl.style.top = arrowCSS.top + 'px';
    arrowEl.style.left = arrowCSS.left + 'px';

    contentEl.style.top = popoverCSS.top + 'px';
    contentEl.style.left = popoverCSS.left + 'px';

    if (checkSafeAreaLeft) {
      if (CSS.supports('left', 'constant(safe-area-inset-left)')) {
        contentEl.style.left = `calc(${popoverCSS.left}px + constant(safe-area-inset-left)`;
      } else if (CSS.supports('left', 'env(safe-area-inset-left)')) {
        contentEl.style.left = `calc(${popoverCSS.left}px + env(safe-area-inset-left)`;
      }
    }

    if (checkSafeAreaRight) {
      if (CSS.supports('right', 'constant(safe-area-inset-right)')) {
        contentEl.style.left = `calc(${popoverCSS.left}px - constant(safe-area-inset-right)`;
      } else if (CSS.supports('right', 'env(safe-area-inset-right)')) {
        contentEl.style.left = `calc(${popoverCSS.left}px - env(safe-area-inset-right)`;
      }
    }

    contentEl.style.transformOrigin = originY + ' ' + originX;

    const backdropAnimation = new Animation(this.plt, baseEl.querySelector('ion-backdrop'));
    backdropAnimation.fromTo('opacity', 0.01, 0.5);

    const wrapperAnimation = new Animation(this.plt, baseEl.querySelector('.popover-wrapper'));
    wrapperAnimation.fromTo('opacity', 0.01, 1);

    this
      .element(this.enteringView.pageRef())
      .easing('ease')
      .duration(100)
      .add(backdropAnimation)
      .add(wrapperAnimation);
  }
}

const POPOVER_IOS_BODY_PADDING = 5;

For global popoverEnter config in app.module.ts add transition to IonicModule:

IonicModule.forRoot(MyApp, {
     popoverEnter: 'custom-popover-enter'
}),

In app.component.ts, import the new class PopoverEnterTransition and then add:

constructor(...private config: Config) {
    this.setCustomTransitions();
}
setCustomTransitions() {
    this.config.setTransition('custom-popover-enter', PopoverEnterTransition);
}

As you can see it's a lot of code, but this allows you to customize the opacity now without breaking the animation. Hope this helps!

Thanks for the issue! We have moved the source code and issues for Ionic 3 into a separate repository. I am moving this issue to the repository for Ionic 3. Please track this issue over there.

Thank you for using Ionic!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

danbucholtz picture danbucholtz  路  3Comments

MrBokeh picture MrBokeh  路  3Comments

SebastianGiro picture SebastianGiro  路  3Comments

Macstyg picture Macstyg  路  3Comments

daveshirman picture daveshirman  路  3Comments