Skip to content
Fix Code Error

Why are CSS keyframe animations broken in Vue components with scoped styling?

March 30, 2021 by Code Error
Posted By: Anonymous

I’m trying to implement a CSS typing indicator in Vue. Without Vue, it looks like this:

_x000D_

_x000D_

.typing-indicator {_x000D_
  background-color: #E6E7ED;_x000D_
  width: auto;_x000D_
  border-radius: 50px;_x000D_
  padding: 20px;_x000D_
  display: table;_x000D_
  margin: 0 auto;_x000D_
  position: relative;_x000D_
  -webkit-animation: 2s bulge infinite ease-out;_x000D_
          animation: 2s bulge infinite ease-out;_x000D_
}_x000D_
.typing-indicator:before, .typing-indicator:after {_x000D_
  content: '';_x000D_
  position: absolute;_x000D_
  bottom: -2px;_x000D_
  left: -2px;_x000D_
  height: 20px;_x000D_
  width: 20px;_x000D_
  border-radius: 50%;_x000D_
  background-color: #E6E7ED;_x000D_
}_x000D_
.typing-indicator:after {_x000D_
  height: 10px;_x000D_
  width: 10px;_x000D_
  left: -10px;_x000D_
  bottom: -10px;_x000D_
}_x000D_
.typing-indicator span {_x000D_
  height: 15px;_x000D_
  width: 15px;_x000D_
  float: left;_x000D_
  margin: 0 1px;_x000D_
  background-color: #9E9EA1;_x000D_
  display: block;_x000D_
  border-radius: 50%;_x000D_
  opacity: 0.4;_x000D_
}_x000D_
.typing-indicator span:nth-of-type(1) {_x000D_
  -webkit-animation: 1s blink infinite 0.3333s;_x000D_
          animation: 1s blink infinite 0.3333s;_x000D_
}_x000D_
.typing-indicator span:nth-of-type(2) {_x000D_
  -webkit-animation: 1s blink infinite 0.6666s;_x000D_
          animation: 1s blink infinite 0.6666s;_x000D_
}_x000D_
.typing-indicator span:nth-of-type(3) {_x000D_
  -webkit-animation: 1s blink infinite 0.9999s;_x000D_
          animation: 1s blink infinite 0.9999s;_x000D_
}_x000D_
_x000D_
@-webkit-keyframes blink {_x000D_
  50% {_x000D_
    opacity: 1;_x000D_
  }_x000D_
}_x000D_
_x000D_
@keyframes blink {_x000D_
  50% {_x000D_
    opacity: 1;_x000D_
  }_x000D_
}_x000D_
@-webkit-keyframes bulge {_x000D_
  50% {_x000D_
    -webkit-transform: scale(1.05);_x000D_
            transform: scale(1.05);_x000D_
  }_x000D_
}_x000D_
@keyframes bulge {_x000D_
  50% {_x000D_
    -webkit-transform: scale(1.05);_x000D_
            transform: scale(1.05);_x000D_
  }_x000D_
}_x000D_
html {_x000D_
  display: table;_x000D_
  height: 100%;_x000D_
  width: 100%;_x000D_
}_x000D_
_x000D_
body {_x000D_
  display: table-cell;_x000D_
  vertical-align: middle;_x000D_
}

_x000D_

<div class="typing-indicator">_x000D_
  <span></span>_x000D_
  <span></span>_x000D_
  <span></span>_x000D_
</div>

_x000D_

_x000D_

_x000D_

– source: http://jsfiddle.net/Arlina/gtttgo93/

The problem is that the animation does not work when adding the scoped attribute to the component’s style definition (<style lang="scss" scoped>). I believe it may be related to keyframes that should be declared globally.

The element with .typing-indicator is in the template of the component with scoped styling.

Does anyone have an idea of how I can allow my component to have scoped styling while making the keyframe animations work?

Solution

Problem

The problem is down to how the Webpack loader for Vue (vue-loader), incorrectly, parses animation names when adding IDs to scoped selectors and other identifiers. This is important because vue-loader’s CSS scoping uses unique attributes added to elements to replicate the behaviour of CSS scoping. While your keyframe names get IDs appended, references to keyframes in animation rules in scoped styles do not.

Your CSS:

@-webkit-keyframes blink {
  50% {
    opacity: 1;
  }
}

@keyframes blink {
  50% {
    opacity: 1;
  }
}
@-webkit-keyframes bulge {
  50% {
    -webkit-transform: scale(1.05);
            transform: scale(1.05);
  }
}
@keyframes bulge {
  50% {
    -webkit-transform: scale(1.05);
            transform: scale(1.05);
  }
}

.typing-indicator {
  ...
  -webkit-animation: 2s bulge infinite ease-out;
          animation: 2s bulge infinite ease-out;
}

.typing-indicator span:nth-of-type(1) {
  -webkit-animation: 1s blink infinite 0.3333s;
          animation: 1s blink infinite 0.3333s;
}
.typing-indicator span:nth-of-type(2) {
  -webkit-animation: 1s blink infinite 0.6666s;
          animation: 1s blink infinite 0.6666s;
}
.typing-indicator span:nth-of-type(3) {
  -webkit-animation: 1s blink infinite 0.9999s;
          animation: 1s blink infinite 0.9999s;
}

Should get transformed to:

@-webkit-keyframes blink-data-v-✘✘✘✘✘✘xx {
  50% {
    opacity: 1;
  }
}

@keyframes blink-data-v-✘✘✘✘✘✘xx {
  50% {
    opacity: 1;
  }
}
@-webkit-keyframes bulge-data-v-✘✘✘✘✘✘xx {
  50% {
    -webkit-transform: scale(1.05);
            transform: scale(1.05);
  }
}
@keyframes bulge-data-v-✘✘✘✘✘✘xx {
  50% {
    -webkit-transform: scale(1.05);
            transform: scale(1.05);
  }
}

.typing-indicator {
  ...
  -webkit-animation: 2s bulge-data-v-✘✘✘✘✘✘xx infinite ease-out;
          animation: 2s bulge-data-v-✘✘✘✘✘✘xx infinite ease-out;
}

.typing-indicator span:nth-of-type(1) {
  -webkit-animation: 1s blink-data-v-✘✘✘✘✘✘xx infinite 0.3333s;
          animation: 1s blink-data-v-✘✘✘✘✘✘xx infinite 0.3333s;
}
.typing-indicator span:nth-of-type(2) {
  -webkit-animation: 1s blink-data-v-✘✘✘✘✘✘xx infinite 0.6666s;
          animation: 1s blink-data-v-✘✘✘✘✘✘xx infinite 0.6666s;
}
.typing-indicator span:nth-of-type(3) {
  -webkit-animation: 1s blink-data-v-✘✘✘✘✘✘xx infinite 0.9999s;
          animation: 1s blink-data-v-✘✘✘✘✘✘xx infinite 0.9999s;
}

However it only get’s transformed to:

@-webkit-keyframes blink-data-v-✘✘✘✘✘✘xx {
  50% {
    opacity: 1;
  }
}

@keyframes blink-data-v-✘✘✘✘✘✘xx {
  50% {
    opacity: 1;
  }
}
@-webkit-keyframes bulge-data-v-✘✘✘✘✘✘xx {
  50% {
    -webkit-transform: scale(1.05);
            transform: scale(1.05);
  }
}
@keyframes bulge-data-v-✘✘✘✘✘✘xx {
  50% {
    -webkit-transform: scale(1.05);
            transform: scale(1.05);
  }
}

.typing-indicator {
  ...
  -webkit-animation: 2s bulge infinite ease-out;
          animation: 2s bulge infinite ease-out;
}

.typing-indicator span:nth-of-type(1) {
  -webkit-animation: 1s blink infinite 0.3333s;
          animation: 1s blink infinite 0.3333s;
}
.typing-indicator span:nth-of-type(2) {
  -webkit-animation: 1s blink infinite 0.6666s;
          animation: 1s blink infinite 0.6666s;
}
.typing-indicator span:nth-of-type(3) {
  -webkit-animation: 1s blink infinite 0.9999s;
          animation: 1s blink infinite 0.9999s;
}

Something to note: in the actual transformation, references to keyframe names in animation rules are missing the -data-v-✘✘✘✘✘✘xx at the end. This is the bug.

Currently (as of 47c3317) the animation name in shorthand animation rule declarations is identified by getting the first value out of splitting the animation rule by any whitespace character[1]. However the formal definition for the animation property states the animation name could appear anywhere within the rule definition.

<single-animation> = <time> || <single-timing-function> || <time> || <single-animation-iteration-count> || <single-animation-direction> || <single-animation-fill-mode> || <single-animation-play-state> || [ none | <keyframes-name> ]

– animation formal syntax[2]

Therefore, while your animation declarations are valid, vue-loader is not able to parse it.

Workaround

The current workaround for this is to move your animation names to the beginning of animation rule declarations. Your keyframe declarations do not need changing, they remain inside the scoped stylesheet. Your animation declarations should now look like this:

.typing-indicator {
  ...
  -webkit-animation: bulge 2s infinite ease-out;
          animation: bulge 2s infinite ease-out;
}
.typing-indicator span:nth-of-type(1) {
  -webkit-animation: blink 1s infinite 0.3333s;
          animation: blink 1s infinite 0.3333s;
}
.typing-indicator span:nth-of-type(2) {
  -webkit-animation: blink 1s infinite 0.6666s;
          animation: blink 1s infinite 0.6666s;
}
.typing-indicator span:nth-of-type(3) {
  -webkit-animation: blink 1s infinite 0.9999s;
          animation: blink 1s infinite 0.9999s;
}

References

  • [1] vue-loader/lib/style-compiler/plugins/scope-id.js#L67 @ 47c3317
  • [2] Definition for animation in the Editor’s Draft of W3C specification CSS Animations Level 1
Answered By: Anonymous

Related Articles

  • Is it possible to apply CSS to half of a character?
  • Active tab issue on page load HTML
  • Navbar not filling width of page when reduced to mobile view
  • How to show title in hover - css / jquery
  • Having trouble with my nav bar/header, It used to work but…
  • How do I keep only the first map and when the game is…
  • Animating Elements in One by One
  • Fix top buttons on scroll of list below
  • CSS Input with width: 100% goes outside parent's bound
  • Jquery fadeToggle Trouble

Disclaimer: This content is shared under creative common license cc-by-sa 3.0. It is generated from StackExchange Website Network.

Post navigation

Previous Post:

how to fire change event on select emberjs

Next Post:

Filtering backbone collection with multiple attributes

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

  • Get code errors & solutions at akashmittal.com
© 2022 Fix Code Error