0

I am trying to get my head around angular animations. I found an article on how to create an animated menu, so I implemented it. This is what I have so far (I have stripped out code not relevant to this question)

import { Component, AfterViewInit, HostBinding, HostListener, ElementRef, Renderer2, OnInit } from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations';
import { distinctUntilChanged, filter, map, pairwise, share, throttleTime } from 'rxjs/operators';
import { fromEvent } from 'rxjs';

enum VisibilityState {
  Visible = 'visible',
  Hidden = 'hidden'
}

enum Direction {
  Up = 'Up',
  Down = 'Down'
}

@Component({
  selector: 'pyb-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.scss'],
  animations: [
    trigger('toggle', [
      state(
        VisibilityState.Hidden,
        style({ height: '10px' })
      ),
      state(
        VisibilityState.Visible,
        style("*")
      ),
      transition('* => *', animate('200ms ease-in'))
    ])
  ]
})
export class HeaderComponent implements AfterViewInit, OnInit {
  private isVisible: boolean = true;

  constructor(
    private renderer: Renderer2,
    private element: ElementRef
  ) { }

  @HostBinding('@toggle')
  get toggle(): VisibilityState {
    if (this.isVisible) {
      this.renderer.removeClass(this.element.nativeElement, 'collapsed');
    }
    else {
      this.renderer.addClass(this.element.nativeElement, 'collapsed');
    }
    return this.isVisible ? VisibilityState.Visible : VisibilityState.Hidden;
  }

  @HostListener('mouseenter')
  mouseEnter() {
    this.isVisible = true;
  }

  @HostListener('mouseleave')
  mouseLeave() {
    this.isVisible = false;
  }

  ngOnInit() {
  }

  ngAfterViewInit() {
    const scroll$ = fromEvent(window, 'scroll').pipe(
      throttleTime(10),
      map(() => window.pageYOffset),
      pairwise(),
      map(([y1, y2]): Direction => (y2 < y1 ? Direction.Up : Direction.Down)),
      distinctUntilChanged(),
      share()
    );

    const scrollUp$ = scroll$.pipe(
      filter(direction => direction === Direction.Up)
    );

    const scrollDown = scroll$.pipe(
      filter(direction => direction === Direction.Down)
    );

    scrollUp$.subscribe(() => {
      this.isVisible = true
    });
    scrollDown.subscribe(() => {
      this.isVisible = false;
    });
  }
}

Now, if the template was just something like this:

<div class="header" #header>
</div>

and the CSS was like this:

:host {
    height: $header-height;
    background-color: $clouds;
    color: $midnight;
    position: fixed;
    top: 0;
    width: 100%;
    z-index: 1000;
}

Everything appears to be working. But, as soon as I add content, it doesn't work properly. My template actually looks like this:

<div class="header" #header>
  <div class="container">
    <div class="row">
      <div class="col" *ngIf="retailers && count && !displayMatches">
        <p class="float-left"><b>{{ count }}</b> {{ category }} available at </p>
        <pyb-text-slider [textArray]="retailers"></pyb-text-slider>
      </div>
      <div class="col" *ngIf="matches && displayMatches">
        <span [ngSwitch]="matches.length">
          <p *ngSwitchCase="0"><b>{{ matches.length }}</b> {{ category }}</p>
          <p *ngSwitchDefault><b>{{ matches.length }}</b> {{ category }} from <b>{{ matches[0].price }}</b></p>
        </span>
        <pyb-matches></pyb-matches>
      </div>
    </div>
  </div>
</div>

And the problem is, the content does not collapse with the menu. I had to add some CSS:

.header {
    visibility: visible;
    opacity: 1;
    transition: visibility .2s, opacity .2s linear;
    padding: 12px 0;
}
&.collapsed .header {
    visibility: hidden;
    opacity: 0;
}

which hides the content, but this isn't really great. Has anyone come across this before and knows how to fix it?

r3plica
  • 13,017
  • 23
  • 128
  • 290

1 Answers1

1

I fixed this by first making my CSS like this:

:host {
    height: $header-height;
    background-color: $clouds;
    color: $midnight;
    position: fixed;
    top: 0;
    width: 100%;
    z-index: 1000;

    transform-origin: top;

    .header {
        transition: visibility .2s, opacity .2s linear;
        visibility: visible;
        opacity: 1;
        padding: 12px 0;
    }

    &.collapsed .header {
        visibility: hidden;
        opacity: 0;
    }
}

And then changing my animation to this:

animations: [
  trigger('toggle', [
    state(
      VisibilityState.Hidden,
      style({ transform: 'scaleY(0.2)' })
    ),
    state(
      VisibilityState.Visible,
      style({ transform: 'scaleY(1)' })
    ),
    transition('* => *', animate('200ms ease-in'))
  ])
]

This collapses the menu nicely, and hides the content at the same time. I am not sure how this will affect performance (mixing CSS with angular animations) but it works.

r3plica
  • 13,017
  • 23
  • 128
  • 290