How to Remove Blazor Components After Exit Animations

How to Remove Blazor Components After Exit Animations
Photo by Steve Johnson / Unsplash

One key part of building delightful UIs is the small animations that make the product feel alive. These often come with a challenge, particularly when you need to make components disappear after their exit animation completes. If you simply remove the component when closing it, the animation won't play and the component will vanish immediately. Looking at the example below:

@if (show)
{
	<div class="@(show ? "enter-animation" : "exit-animation")">
		Modal Content
	</div>
}

@code {
	private bool show { get; set; }

	public void OpenModal()
	{
		show = true;
	}

	public void CloseModal()
	{
		show = false;
	}
}

We have the CSS classes enter-animation and exit-animation which apply a CSS animation. The problem is that if we set show to false, the component will disappear immediately, not triggering the exit animation.
Let's see how we can make Blazor wait for the animation to complete before removing the component from the DOM.

The animationend event

The animationend event is a browser native event that is fired when a CSS animation has completed. The event provides us with the animation name, allowing us to check if the animation-exit animation has finished, and if so, we can hide the component.
There is just one issue. While Blazor provides us with a lot of browser native event handlers, onanimationend is not one of those. We need to create our own custom event handler to be able to use this event (and others like it) in our components.

Registering a custom event handler in Blazor

To be able to access JS events in our Blazor components, we need to register a custom event handler.

Adding the Event Handler

First, to handle custom events in Blazor we need to register them in an EventHandlers class, using the [EventHandler] attribute.

[EventHandler("onanimationend", typeof(EventArgs),
    enableStopPropagation: true, enablePreventDefault: true)]
public static class EventHandlers
{
}

For each event that we want to handle in our Blazor components, we need to add an EventHandler attribute. The first parameter is the name of the event handler we want to enable, the second is the type of the arguments passed to the event handler, and then whether we want to allow the handler to stop the propagation of the event and to prevent the default behavior.

💡
For the event handlers to be recognized and work you have to ensure:
- The class is public
- The class is called exactly EventHandlers
- The class is in a namespace from your app, and it must be imported by the component using an event handler

Since the event is a native browser event, this is all we need to handle the event on the Blazor components.

@if (show)
{
	<div class="@(show ? "enter-animation" : "exit-animation")" @onanimationend="HandleAnimationEnd">
		Modal Content
	</div>
}

@code {
	private bool show { get; set; }

	public void OpenModal()
	{
		show = true;
	}

	public void CloseModal()
	{
		show = false;
	}

	private void HandleAnimationEnd(AnimationEventArgs e)
	{
	    // Hide component if animation is the exit animation
	}
}

While this will work, there is a problem. We don't have access to the information in the event. This means that when the enter animation or the exit animation end, they will both trigger the handler and we'll have no way of knowing which animation has ended.

Registering custom event properties

To be able to pass event information to the Blazor event handlers we need to register these custom properties with JavaScript.

Start by creating a file wwwroot/{PACKAGE ID/ASSEMBLY NAME}.lib.module.js. For example, if your project is named MyBlogApp, the file would be wwwroot/MyBlogApp.lib.module.js. The assembly name will typically be the name of your project.

In this file, we will register the event information with the following code:

export function afterStarted(blazor) {
  blazor.registerCustomEventType('onanimationend', {
    createEventArgs: event => {
	    return {
	        animationName: event.animationName,
	        elapsedTime: event.elapsedTime,
	        pseudoElement: event.pseudoElement
	    };
	}
  });
}

Info

This example applies to Blazor Server and Web Assembly app. For a Blazor Web App, you have to export the function afterWebStarted instead.

If you look at the documentation, you'll see them using two properties on the second parameter:

  • browserEventName: this is the name of the JS event that will trigger the custom event in Blazor. For browser native events you must not set this property, as it will cause the event to be triggered twice
  • createEventArgs: this is the function that allows you to map the information from the browser event into the custom event. In this case, we are mapping all the properties from the AnimationEvent interface

Now we just need to create a C# class that maps the event args that we added above.

public class AnimationEventArgs : EventArgs
{
	public string AnimationName { get; set; }

	public float ElapsedTime { get; set; }

	public string PseudoElement { get; set; }
}

Now we can update our EventHandlers class and our component to take the AnimationEventArgs.

[EventHandler("onanimationend", typeof(AnimationEventArgs),
    enableStopPropagation: true, enablePreventDefault: true)]
public static class EventHandlers
{
}

Handling the removal of the component

Now that we have everything we need, let's finish up our component.

@if (render)
{
	<div class="@(show ? "enter-animation" : "exit-animation")" @onanimationend="HandleAnimationEnd">
		Modal Content
	</div>
}

@code {
	private bool show { get; set; }
	private bool render { get; set; }

	public void OpenModal()
	{
		show = true;
		render = true;
	}

	public void CloseModal()
	{
		show = false;
	}

	private void HandleAnimationEnd(AnimationEventArgs e)
	{
	    if (e.AnimationName == "exit-animation-name")
	    {
		    render = false;
	    }
	}
}

We introduced an additional property called render, which is what determines if the component is rendered or not.
When we open the modal we set render and show to true, which will trigger the enter animation.
When we close the modal, we only set show to false, which triggers the exit animation. Then, once the animation ends, the handler is called and render is set to false, removing the component from the DOM.

💡
Keep in mind that the animation name is set in the CSS with @keyframes animationame. It is not the name of the class.

Conclusion

By separating animation state from render state, we can create smooth exit animations that complete before components are removed from the DOM. This pattern leverages Blazor's custom event handlers to bridge the gap between CSS animations and component lifecycle management, giving you full control over when components appear and disappear.

Beyond animation timing, custom event handlers unlock many other possibilities:

  • Listening to events from JS libraries that you want to integrate with Blazor
  • Listening to transition or other browser native events
  • Converting browser native events into events with additional properties from JS

At the end of the day, the custom event handlers help to bridge the gap from JS and Blazor, making it much easier to listen to what happens in the browser and react accordingly in Blazor.