ng-bootstrap collapse: How to apply animations?

后端 未结 4 1087
猫巷女王i
猫巷女王i 2021-02-19 09:39

I\'m using Collapse: https://ng-bootstrap.github.io/#/components/collapse

However, it does not animate; even not on the demo site. How should I implement this??

相关标签:
4条回答
  • 2021-02-19 09:48

    I've created a directive using only bootstrap classes that performs the same original bootstrap effect using only angular-animation, check it out!

    Live example

    Directive code

    import {Directive, ElementRef, HostBinding, Input, OnChanges, OnInit, SimpleChanges} from '@angular/core';
    import {animate, AnimationBuilder, AnimationPlayer, keyframes, style} from '@angular/animations';
    
    @Directive({
      selector: '[appCollapseAnimated]'
    })
    export class CollapseAnimatedDirective implements OnChanges, OnInit {
      private static readonly SHOW_STYLE = 'show';
      private static readonly COLLAPSING = 'collapsing';
    
      @Input('appCollapseAnimated')
      collapsed = true;
    
      @Input()
      skipClosingAnimation = false;
    
      @HostBinding('class.collapse')
      private readonly addCollapseClass = true;
    
      private currentEffect: AnimationPlayer;
      private _closeEffect: AnimationPlayer;
      private _openEffect: AnimationPlayer;
    
      constructor(private el: ElementRef,
                  private builder: AnimationBuilder) {
    
      }
    
      ngOnInit(): void {
        if (!this.collapsed) {
          this.getClassList().add(CollapseAnimatedDirective.SHOW_STYLE);
        }
      }
    
      private get openEffect(): AnimationPlayer {
        if (!this._openEffect) {
          this._openEffect = this.builder.build(animate('500ms', keyframes([
            style({height: '0'}),
            style({height: '*'}),
          ]))).create(this.el.nativeElement);
        }
        this._openEffect.onDone(() => this.effectDone());
        return this._openEffect;
      }
    
    
      private get closeEffect(): AnimationPlayer {
        if (!this._closeEffect) {
          this._closeEffect = this.builder.build(animate('500ms', keyframes([
            style({height: '*'}),
            style({height: '0'}),
          ]))).create(this.el.nativeElement);
        }
        this._closeEffect.onDone(() => this.effectDone());
        return this._closeEffect;
      }
    
      private effectDone() {
        if (this.collapsed) {
          this.getClassList().remove(CollapseAnimatedDirective.SHOW_STYLE);
        }
        this.getClassList().remove(CollapseAnimatedDirective.COLLAPSING);
        if (this.currentEffect) {
          this.currentEffect.reset();
          this.currentEffect = null;
        }
      }
    
      ngOnChanges(changes: SimpleChanges): void {
        if (changes.collapsed && !changes.collapsed.firstChange) {
          if (changes.collapsed.previousValue === true && changes.collapsed.currentValue === false) {
            this.startOpening();
          }
          if (changes.collapsed.previousValue === false && changes.collapsed.currentValue === true) {
            this.startClosing();
          }
        }
      }
    
      private startOpening(): void {
        this.getClassList().add(CollapseAnimatedDirective.SHOW_STYLE);
        const effect = this.openEffect;
        this.playEffect(effect);
      }
    
      private getClassList() {
        const nativeElement = this.el.nativeElement as HTMLElement;
        return nativeElement.classList;
      }
    
      private startClosing(): void {
        const effect = this.closeEffect;
        if (this.skipClosingAnimation) {
          this.effectDone();
        } else {
          this.playEffect(effect);
        }
      }
    
      private playEffect(effect: AnimationPlayer) {
        if (!this.currentEffect) {
          this.getClassList().add(CollapseAnimatedDirective.COLLAPSING);
          this.currentEffect = effect;
          this.currentEffect.play();
        }
      }
    }
    

    Use it in your template:

    <button class="btn btn-primary" (click)="collapsed = !collapsed">
      Toggle
    </button>
    <div [appCollapseAnimated]="collapsed" class="beautiful-div border border-secondary mt-5">
      this should collapse with the default bootstrap behaviour
    </div>
    
    0 讨论(0)
  • 2021-02-19 09:57

    Here's a good way to do it, I think, and it works both for revealing and collapsing: (though it doesn't really need ng-bootstrap anymore)

    template.html:

    <button (click)="isCollapsed = !isCollapsed">Toggle</button>
    <p [@smoothCollapse]="isCollapsed?'initial':'final'">
        your data here
    </p>
    

    .

    component.ts:

    import { trigger, state, style, animate, transition } from '@angular/animations';
    
    @Component({
      selector: 'app-my-component',
      templateUrl: './template.html',
      styleUrls: ['./style.scss'],
      animations: [
        trigger('smoothCollapse', [
          state('initial', style({
            height:'0',
            overflow:'hidden',
            opacity:'0'
          })),
          state('final', style({
            overflow:'hidden',
            opacity:'1'
          })),
          transition('initial=>final', animate('750ms')),
          transition('final=>initial', animate('750ms'))
        ]),
      ]
    })
    export class MyComponent ...
    

    Details:

    • initial height:0 allows to start from nothing
    • not specifying a final height let the element stop growing when it's all displayed
    • overflow:hidden everywhere so your element doesn't run on other elements
    • opacity from 0 to 1 makes it nicer (in my opinion)
    • An important thing which took me some time to realize is to NOT put [ngbCollapse]="isCollapsed" in the <p> otherwise it breaks all the animation. And we don't need it since we set the height to 0

    Hope it helps, I spent a day on it :P

    0 讨论(0)
  • 2021-02-19 10:09

    inside your component you can add something like this:

     animations: [
        trigger('expandCollapse', [
                    state('open', style({height: '100%', opacity: 1})),
                    state('closed', style({height: 0, opacity: 0})),
                    transition('* => *', [animate('100ms')])
                ]),
     ]
    
    
     <div [ngbCollapse]="isCollapsed" [@expandCollapse]="isCollapsed ? 'closed' : 'open'"> ... </div>
    

    See more details here https://angular.io/guide/animations#animating-a-simple-transition

    0 讨论(0)
  • 2021-02-19 10:12

    Because they use "display: none" to hide and "display: block" to show element, you can't apply "transition" CSS property.

    So, force display block, manage height & opacity to toggle hide/show :

    .collapse, .collapse.in {
      display: block !important;
      transition: all .25s ease-in-out;
    }
    
    .collapse {
     opacity: 0;
     height: 0;
    }
    
    .collapse.in {
      opacity: 1;
      height: 100%;
    }
    

    With basic transition and opacity/height, it seem to be more smooth.

    You can make your own animation with keyframe and apply to .collapse.in to get better toggle hide/show experience.

    And then, if you use Angular 2 in your project, you can switch to ng2-bootstrap : http://valor-software.com/ng2-bootstrap/

    0 讨论(0)
提交回复
热议问题