为啥 CSS 关键帧动画在具有范围样式的 Vue 组件中被破坏?

Posted

技术标签:

【中文标题】为啥 CSS 关键帧动画在具有范围样式的 Vue 组件中被破坏?【英文标题】:Why are CSS keyframe animations broken in Vue components with scoped styling?为什么 CSS 关键帧动画在具有范围样式的 Vue 组件中被破坏? 【发布时间】:2018-04-27 07:05:52 【问题描述】:

我正在尝试在 Vue 中实现 CSS 类型指示器。如果没有 Vue,它看起来像这样:

.typing-indicator 
  background-color: #E6E7ED;
  width: auto;
  border-radius: 50px;
  padding: 20px;
  display: table;
  margin: 0 auto;
  position: relative;
  -webkit-animation: 2s bulge infinite ease-out;
          animation: 2s bulge infinite ease-out;

.typing-indicator:before, .typing-indicator:after 
  content: '';
  position: absolute;
  bottom: -2px;
  left: -2px;
  height: 20px;
  width: 20px;
  border-radius: 50%;
  background-color: #E6E7ED;

.typing-indicator:after 
  height: 10px;
  width: 10px;
  left: -10px;
  bottom: -10px;

.typing-indicator span 
  height: 15px;
  width: 15px;
  float: left;
  margin: 0 1px;
  background-color: #9E9EA1;
  display: block;
  border-radius: 50%;
  opacity: 0.4;

.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;


@-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);
  

html 
  display: table;
  height: 100%;
  width: 100%;


body 
  display: table-cell;
  vertical-align: middle;
<div class="typing-indicator">
  <span></span>
  <span></span>
  <span></span>
</div>

– 来源:http://jsfiddle.net/Arlina/gtttgo93/

问题是在组件的样式定义(&lt;style lang="scss" scoped&gt;)中添加scoped属性时动画不起作用。我相信它可能与应该全局声明的关键帧有关。

.typing-indicator 的元素在具有作用域样式的组件的模板中。

有没有人知道如何在使关键帧动画正常工作的同时允许我的组件具有范围样式?

【问题讨论】:

【参考方案1】:

我遇到了同样的问题,第一个答案确实告诉我为什么它不起作用,但解决方法部分并没有完全解决我的问题......这是我的代码:

/* Animations */
@keyframes moveOut1 
    from 
        transform: translateY(0) scale(0);
    
    3% 
        transform: translateY(0.2em) scale(1);
    
    97% 
        transform: translateY(7.3em) scale(1);
    
    to 
        transform: translateY(7.5em) scale(0);
    

@keyframes moveOut2 
    from 
        transform: rotate(60deg) translateY(0) scale(0);
    
    3% 
        transform: rotate(60deg) translateY(0.2em) scale(1);
    
    97% 
        transform: rotate(60deg) translateY(7.3em) scale(1);
    
    to 
        transform: rotate(60deg) translateY(7.5em) scale(0);
    

@keyframes moveOut3 
    from 
        transform: rotate(120deg) translateY(0) scale(0);
    
    3% 
        transform: rotate(120deg) translateY(0.2em) scale(1);
    
    97% 
        transform: rotate(120deg) translateY(7.3em) scale(1);
    
    to 
        transform: rotate(120deg) translateY(7.5em) scale(0);
    

@keyframes moveOut4 
    from 
        transform: rotate(180deg) translateY(0) scale(0);
    
    3% 
        transform: rotate(180deg) translateY(0.2em) scale(1);
    
    97% 
        transform: rotate(180deg) translateY(7.3em) scale(1);
    
    to 
        transform: rotate(180deg) translateY(7.5em) scale(0);
    

@keyframes moveOut5 
    from 
        transform: rotate(240deg) translateY(0) scale(0);
    
    3% 
        transform: rotate(240deg) translateY(0.2em) scale(1);
    
    97% 
        transform: rotate(240deg) translateY(7.3em) scale(1);
    
    to 
        transform: rotate(240deg) translateY(7.5em) scale(0);
    

@keyframes moveOut6 
    from 
        transform: rotate(300deg) translateY(0) scale(0);
    
    3% 
        transform: rotate(300deg) translateY(0.2em) scale(1);
    
    97% 
        transform: rotate(300deg) translateY(7.3em) scale(1);
    
    to 
        transform: rotate(300deg) translateY(7.5em) scale(0);
    

@keyframes ripple 
    from,
    to 
        width: 0.2em;
    
    33% 
        width: 2.4em;
    

所以我问了一个朋友,他给我的解决方案就是把css代码放在外面,然后通过

导入到vue组件中
<style>
@import url(./css_file_name.css);
</style>

但我不明白这背后的机制......但对我来说,只要它有效就可以了。

【讨论】:

【参考方案2】:

问题

问题在于 Vue (vue-loader) 的 Webpack 加载器在将 ID 添加到范围选择器和其他标识符时如何错误地解析动画名称。这很重要,因为 vue-loader 的 CSS 作用域使用添加到元素的独特属性来复制 CSS 作用域的行为。虽然您的关键帧名称会附加 ID,但在作用域样式中对动画规则中关键帧的引用不会。

你的 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;

应该转化为:

@-webkit-keyframes blink-data-v-xxxxxxxx 
  50% 
    opacity: 1;
  


@keyframes blink-data-v-xxxxxxxx 
  50% 
    opacity: 1;
  

@-webkit-keyframes bulge-data-v-xxxxxxxx 
  50% 
    -webkit-transform: scale(1.05);
            transform: scale(1.05);
  

@keyframes bulge-data-v-xxxxxxxx 
  50% 
    -webkit-transform: scale(1.05);
            transform: scale(1.05);
  


.typing-indicator 
  ...
  -webkit-animation: 2s bulge-data-v-xxxxxxxx infinite ease-out;
          animation: 2s bulge-data-v-xxxxxxxx infinite ease-out;


.typing-indicator span:nth-of-type(1) 
  -webkit-animation: 1s blink-data-v-xxxxxxxx infinite 0.3333s;
          animation: 1s blink-data-v-xxxxxxxx infinite 0.3333s;

.typing-indicator span:nth-of-type(2) 
  -webkit-animation: 1s blink-data-v-xxxxxxxx infinite 0.6666s;
          animation: 1s blink-data-v-xxxxxxxx infinite 0.6666s;

.typing-indicator span:nth-of-type(3) 
  -webkit-animation: 1s blink-data-v-xxxxxxxx infinite 0.9999s;
          animation: 1s blink-data-v-xxxxxxxx infinite 0.9999s;

但是它只会被转换为:

@-webkit-keyframes blink-data-v-xxxxxxxx 
  50% 
    opacity: 1;
  


@keyframes blink-data-v-xxxxxxxx 
  50% 
    opacity: 1;
  

@-webkit-keyframes bulge-data-v-xxxxxxxx 
  50% 
    -webkit-transform: scale(1.05);
            transform: scale(1.05);
  

@keyframes bulge-data-v-xxxxxxxx 
  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;

注意事项:在实际变换中,动画规则中关键帧名称的引用缺少末尾的-data-v-xxxxxxxx。这是错误。

目前(截至47c3317),速记动画规则声明中的动画名称是通过从任何空白字符分割动画规则中获取第一个值来标识的[1]。然而,动画属性的正式定义表明动画名称​​可以出现在规则定义中的任何位置。

&lt;single-animation&gt; = &lt;time&gt; || &lt;single-timing-function&gt; || &lt;time&gt; || &lt;single-animation-iteration-count&gt; || &lt;single-animation-direction&gt; || &lt;single-animation-fill-mode&gt; || &lt;single-animation-play-state&gt; || [ none | &lt;keyframes-name&gt; ]

animation 正式语法[2]

因此,虽然您的动画声明有效,但 vue-loader 无法解析它。

解决方法

目前的解决方法是将动画名称移动到动画规则声明的开头。您的关键帧声明不需要更改,它们保留在作用域样式表中。您的动画声明现在应该如下所示:

.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;

参考文献

[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

【讨论】:

谢谢。在你的解释中,我看不到关键帧应该放在哪里。 “将动画名称移动到动画规则声明的开头”是什么意思?我终于在另一个样式块中声明了我的关键帧,该样式块与作用域的块分开。它有效,但可能会影响使用我的插件的网站。解决方案是为动画(GUID)选择长(并希望是唯一的)名称 @PierreClocher:查看animation.typing-indicator.typing-indicator span:nth-of-type(n) 声明。他们目前遵循以下模式:animation: &lt;animation-duration&gt; &lt;animation-name&gt; &lt;...other animation values&gt;(例如2s bulge infinite ease-out1s blink infinite 0.3333s)。您需要做的是将animation-name 放在规则声明的开头:animation: &lt;animation-name&gt; &lt;animation-duration&gt; &lt;...other animation values&gt;(例如bulge 2s infinite ease-outblink 1s infinite 0.3333s)。您的 @keyframe 声明可以保持范围样式。 在遇到spin.js 的问题后找到了这个答案,其中动画名称由 javascript 解析器根据通过 JSON 对象传入的设置动态构建。因此,如果不重新编写整个插件,建议的解决方法是不可能的。相反,需要做的是(正如@PierreClocher 所暗示的),在更高级别定义动画@keyframes 声明。我将它们构建到我的 webkit CSS 编译中,以便它们随后包含在整个页面的整体 CSS 文件中。 @cartbeforehorse:是的,将关键帧声明移动到全局范围也可以解决这个问题。但是,对于这个特定的问题,它并不能满足将关键帧声明保持在特定 Vue 组件范围内的最初目标。 @cartbeforehorse 对于带有 Vue v3 的 spin.js,我在自定义 Spinner 组件中使用它。我发现它就像添加以下内容一样简单:&lt;style lang="scss"&gt;@import '~spin.js/spin.css';&lt;/style&gt;

以上是关于为啥 CSS 关键帧动画在具有范围样式的 Vue 组件中被破坏?的主要内容,如果未能解决你的问题,请参考以下文章

Css3之高级-7 Css动画(概述关键帧动画属性)

CSS3@keyframes规则和animation动画

CSS3之动画

如何停止 CSS 关键帧动画?

具有内联样式的关键帧 ReactJS

CSS3 动画(关键帧动画)