Robbie Mills
Robbie Mills

Reputation: 2945

Animate a hamburger menu from Tailwind in Angular

I'm using Tailwind UI in an Angular application and can open / close the hamburger menu with the following

<header>
  <div class="-mr-2 -my-2 md:hidden">
    <button type="button" (click)="toggleMobileMenu()"
      class="bg-white rounded-md p-2 inline-flex items-center justify-center text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500">
      <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"
        aria-hidden="true">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
      </svg>
    </button>
  </div>
  <div class="absolute z-30 top-0 inset-x-0 p-2 transition transform origin-top-right md:hidden" *ngIf="mobileMenuOpen">
    <div class="rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 bg-white divide-y-2 divide-gray-50">

      <div class="flex items-center justify-between">
        <button (click)="toggleMobileMenu()" type="button"
          class="bg-white rounded-md p-2 inline-flex items-center justify-center text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500">
          <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"
            aria-hidden="true">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
          </svg>
        </button>
      </div>
      <div class="py-6 px-5">
        <div class="grid gap-4">
          <a routerLink="/" class="text-base font-medium text-gray-900 hover:text-gray-700">
            Home
          </a>
        </div>
      </div>
    </div>
  </div>
</header>


toggleMobileMenu() {
  this.mobileMenuOpen = !this.mobileMenuOpen;
}

This is a really simple example.

How do I animate this?

The instructions are :

    Mobile menu, show/hide based on mobile menu state.

    Entering: "duration-200 ease-out"
      From: "opacity-0 scale-95"
      To: "opacity-100 scale-100"
    Leaving: "duration-100 ease-in"
      From: "opacity-100 scale-100"
      To: "opacity-0 scale-95"

and I don't know how to translate that to an angular animation

Upvotes: 1

Views: 6341

Answers (3)

Damon
Damon

Reputation: 4524

This thread really helped point me in the right direction. Using Angular 14 & TailwindUI Here is what ended up working for me.

I needed to render a notification to display the response from an http request. I have created a NotificationService that I subscribe to that handles the show (emit). I found this SO thread(s) to be super helpful accomplishing that.

// app.component.ts

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css'],
    providers: [],
    animations: [
        trigger('renderNotification', [
            transition(':enter', [
                style({
                    transform: 'translateY(0.5rem)',
                    opacity: 0,
                }),
                animate(
                    '150ms 0ms ease-out',
                    style({
                        transform: 'translateY(0)',
                        opacity: 1,
                    })
                ),
            ]),
            transition(':leave', [
                style({
                    transform: 'translateY(0)',
                    opacity: 1,
                }),
                animate(
                    '100ms 0ms ease-in',
                    style({
                        transform: 'translateX(0.5rem)',
                        opacity: 0,
                    })
                ),
            ]),
        ]),
    ],
})
export class AppComponent implements OnInit {
    showNotification: boolean = false;
    ...
}
<!-- app.component.html -->

<div class="flex h-full">
    <div
        aria-live="assertive"
        class="pointer-events-none fixed inset-0 z-50 flex items-end px-4 py-6 sm:items-start sm:p-6"
    >
        <div class="flex w-full flex-col items-center space-y-4 sm:items-end">
            <app-notifications
                class=""
                [@renderNotification]="notification.show"
                *ngIf="showNotification"
                [type]="notification.type"
                [title]="notification.title"
                [body]="notification.body"
                (closeNotificationEvent)="closeNotification()"
            ></app-notifications>
        </div>
    </div>

    ...

    <div class="flex min-w-0 flex-1 flex-col overflow-hidden">
        <router-outlet></router-outlet>
    </div>
</div>

Hope this helps someone else!

Upvotes: 0

HirenParekh
HirenParekh

Reputation: 3735

Here is an approach that uses only tailwind classes for animation. Here is a stackblitz demo

And here is a brief explanation.

Initially for the mobile menu div we set the tailwind classes which keeps the div in a hidden state. and to do so we have used.

scale-95 , opacity-0 and we have also used pointer-events-none to avoid interactions in hidden state.

and when the menu is active we add the following classes.

scale-100, opacity-100 and pointer-events-auto.

and to animate these properties we have used the transition class on the div and the duration-200 class for the transition duration.

    <link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
    <header>
        <div class="-mr-2 -my-2 md:hidden">
            <button type="button" (click)="toggleMobileMenu()"
          class="bg-white rounded-md p-2 inline-flex items-center justify-center text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500">
          <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"
            aria-hidden="true">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
          </svg>
        </button>
        </div>
<!-- here we have added transition transform scale-95 opacity-0 duration-200 pointer-events-none -->
        <div class="absolute z-30 top-0 inset-x-0 p-2 transition transform scale-95 opacity-0 duration-200 pointer-events-none md:hidden"
            [ngClass]="{'scale-100 pointer-events-auto opacity-100': mobileMenuOpen}">
<!-- then toggle classes scale-100 opacity-100 and pointer-events-auto -->
            <div class="rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 bg-white divide-y-2 divide-gray-50">
                <div class="flex items-center justify-between">
                    <button (click)="toggleMobileMenu()" type="button"
              class="bg-white rounded-md p-2 inline-flex items-center justify-center text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500">
              <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"
                aria-hidden="true">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
              </svg>
            </button>
                </div>
                <div class="py-6 px-5">
                    <div class="grid gap-4">
                        <a routerLink="/" (click)="toggleMobileMenu()" class="text-base font-medium text-gray-900 hover:text-gray-700">
                            Home
                        </a>
                    </div>
                </div>
            </div>
        </div>
    </header> 

So, just by using the right tailwind classes, you can achieve the animation effect.

Upvotes: 3

Owen Kelvin
Owen Kelvin

Reputation: 15083

Below is an approach I would follow.

  1. Ensure to import BrowserAnimationsModule
@NgModule({
  imports: [ BrowserModule, BrowserAnimationsModule, ... ],
  ...
})
  1. Under your @Component decorator create animations array as below
@Component({
  ,,,,
  animations: [
    trigger("openClose", [
      // ...
      state(
        "open",
        style({
          opacity: 1,
          transform: "scale(1, 1)"
        })
      ),
      state(
        "closed",
        style({
          opacity: 0,
          transform: "scale(0.95, 0.95)"
        })
      ),
      transition("open => closed", [animate("100ms ease-in")]),
      transition("closed => open", [animate("200ms ease-out")])
    ])
  ]
})

In the animations array we have included below

  • trigger("openClose", [ ...]) which defines the name of our trigger. This will be used in the HTML template to bind to a dom element

  • define 2 state using state('stateName' In our example above, the state names are open and closed

  • Under state we define the styles using style({ ... })

  • We finally define transition using transition(...). This will define the timing and animation style

More on animations Angular animations

  1. Define a getter for the state
get openCloseTrigger() {
  return this.mobileMenuOpen ? "open" : "closed";
}
  1. Bind state to getter to in the HTML
<div [@openClose]="openCloseTrigger" ...> 
  <!-- Other stuff here -->
</div>

Everything should be ready and your menu should animate as desired

See this demo on stackblitz

Closing the browser on navigation We can leverage NavigationEnd event to achieve this functionality. When a user is on a specific page, then they will not navigate away from the page hence no NavigationEnd. The menu will remain open but when they navigate to another page, NavigationEnd event will be called and the menu will close

This is the approach

constructor(private router: Router) {}
  navigationEnd$ = this.router.events.pipe(
    filter(event => event instanceof NavigationEnd),
    tap(() => (this.mobileMenuOpen = false))
  );

  ngOnInit() {
    this.navigationEnd$.subscribe();
  }

See this functionality on stackblitz

Upvotes: 7

Related Questions