Skip to main content
Ben Nadel at RIA Unleashed (Nov. 2009) with: Kim Andi
Ben Nadel at RIA Unleashed (Nov. 2009) with: Kim Andi ( @msandi )

CSS @keyframes Animations Are Not Scoped With Emulated View Encapsulation In Angular 4.4.6

By on

One of the coolest features of Angular is that it provides "emulated view encapsulation" for CSS. This means that we can write CSS that targets our component views without having to worry about naming collisions. Emulated view encapsulation is powered by dynamic attributes that Angular injects into each component View as well as into the component's CSS, essentially locking the CSS rules down to attribute-selectors. One thing that tripped me up, however, is that this emulated view encapsulation does not apply to CSS @keyframes animations. CSS @keyframes animations remain in the global namespace and can collide across component definitions and rendering.

Run this demo in my JavaScript Demos project on GitHub.

To see this in action, all we have to do is render two components on the page at the same time that both attempt to use a CSS @keyframes animation with the same name. In this case, I'm going to have my app component and a child-component both use an @keyframes animation called "enter-animation". Here's the app component:

// Import the core angular services.
import { Component } from "@angular/core";

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

@Component({
	selector: "my-app",
	styles: [`
		:host {
			animation: enter-animation 10s ease ;
			border: 3px solid #EAEAEA ;
			box-sizing: border-box ;
			display: block ;
			font-size: 16px ;
			padding: 20px 20px 20px 20px ;
		}

		@keyframes enter-animation {
			from {
				opacity: 0.0 ;
			}
			to {
				opacity: 1.0 ;
			}
		}

		child-component {
			margin: 20px 0px 20px 0px ;
		}
	`],
	template:
	`
		<div>
			This is the <strong>App</strong> component.
		</div>

		<child-component></child-component>

		<div>
			When this runs, you will see that the <strong>height</strong> animation of
			the child-component <strong>overrides the opacity</strong> animation of the
			app component because they are both named <strong>"enter-animation"</strong>.
		</div>
	`
})
export class AppComponent {
	// ...
}

As you can see, the app component is trying to use an @keyframes animation, "enter-animation", that fades the entire component tree into view using "opacity". Now, let's look at the child-component that is going to be rendered as part of the initial app component view:

// Import the core angular services.
import { Component } from "@angular/core";

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

@Component({
	selector: "child-component",
	styles: [`
		:host {
			animation: enter-animation 10s ease ;
			border: 3px solid #CCCCCC ;
			box-sizing: border-box ;
			display: block ;
			height: 100px ;
			padding: 20px 20px 20px 20px ;
		}

		@keyframes enter-animation {
			from {
				height: 0px ;
			}
			to {
				height: 100px ;
			}
		}
	`],
	template:
	`
		This is the <strong>Child</strong> component.
	`
})
export class ChildComponent {
	// ...
}

As you can see, the child component is also trying to use an @keyframes animation named, "enter-animation". Only, this one is using a "height" based animation, not an opacity animation.

Both of these animations run for 10-seconds, so it's easy to see which @keyframes animation is affecting which component. And, if we run this Angular application in the browser, we get the following mid-animation rendering:

CSS @keyframes animations are not scoped to a component with emulated view encapsulation in Angular 4.4.6.

As you can see, the app component is not animating its opacity as we might expect. Instead, and it's hard to see this in the screenshot, the app component is animating its height, just like the child-component. This is because the @keyframes animation doesn't get scoped in the emulated view encapsulation, allowing the "enter-animation" definition in the child-component to override the "enter-animation" definition in the app component.

If we look at the injected style tags, this becomes more obvious:

CSS @keyframes animation names exist in the global CSS namespace in Angular 4.4.6 and can override each other.

While the standard CSS rule blocks get scoped with dynamic _nghost and _ngcontent attributes, the @keyframes animation names are unaltered. This allows the "enter-animation" definition in the child-component to override the "enter-animation" definition in the app component.

This is just a caveat to be aware of in the emulated encapsulation feature of Angular 4.4.6. And, it might be a good reason to try to move your animations into the core animation support of the Angular framework itself. The core animations module seems to be getting more powerful with every release. It's probably time that I start weaving it into more of my Angular development R&D.

Want to use code from this post? Check out the license.

Reader Comments

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel