Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Valerie Poreaux
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Valerie Poreaux@valerieporeaux )

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

By Ben Nadel 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.



Looking For A New Job?

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader Comments

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.