CSS @keyframes Animations Are Not Scoped With Emulated View Encapsulation In Angular 4.4.6
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:
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:
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 don't know when it was added; but at least as of Angular 18,
@keyframes
are now being scoped to the component! Amazing!Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →