【问题标题】:How to synchronize CSS animations that were started/restarted at different times如何同步在不同时间启动/重启的 CSS 动画
【发布时间】:2022-01-03 12:42:08
【问题描述】:

我正在尝试为接收相同动画类的多个页面元素同步 CSS 动画。

好像有很多关于这个主题的帖子但是没有统一的解决方案,而且我找到的解决方案似乎都不适用于这个案例。

我在这里准备了一个 jsfiddle 来演示这个问题:

https://jsfiddle.net/gdf7szo5/1/

如果您单击任何字母,它将启动该字母的动画。如果再次单击,它将切换到不同的动画,如果您第三次单击,它将设置字母完全没有动画。

想要的效果是使一个动画中所有闪烁的字母相互同步,而另一个动画中的任何字母相互同步。需要明确的是,我并不是要同步这两个动画——我只是希望具有给定动画的所有元素相互同步。

但目前,如果一个字母显示动画,而您将另一个字母设置为相同的动画,除非您有绝对完美的时机,这两个字母的动画将不同步,即使它们是相同的动画。

下面是正在运行的代码:

HTML:

<div>
  <span id="spA" onclick="toggleFx('spA')">A</span>
  <span id="spB" onclick="toggleFx('spB')">B</span>
  <span id="spC" onclick="toggleFx('spC')">C</span>
  <span id="spD" onclick="toggleFx('spD')">D</span>
</div>

CSS:

div {
  display: flex;
  flex-flow: column;
}

span {
  animation-name: pulse_active;
}

span.pulse {
    color: #f00;
    animation: pulse_active 1.5s ease-in infinite;
}

span.pulse_invert {
  color: #00f;
  animation: pulse_inverted 3s ease-in infinite;
}

@keyframes pulse_active {
    0% {
        opacity: 0;
    }
    50% {
        opacity: 0.66;
    }
    100% {
        opacity: 0;
    }
}

@keyframes pulse_inverted {
    0% {
        opacity: 1;
    }
    50% {
        opacity: 0.33;
    }
    100% {
        opacity: 1;
    }
}

JS:

let pulseNormal = {};
let pulseInvert = {};

function toggleFx(spanID) {

    let spanTarget = document.getElementById(spanID);
  
  // shift setting
  if (spanID in pulseInvert) {
  console.log('y');
  console.log(Object.keys(pulseInvert));
    delete pulseInvert[spanID]
  console.log(Object.keys(pulseInvert));
  } else if (spanID in pulseNormal) {
    pulseInvert[spanID] = true;
    delete pulseNormal[spanID];
  } else {
    pulseNormal[spanID] = true
  }
  
  // update display
  for (let sp of 'ABCD') {
    let span = document.getElementById(`sp${sp}`);
    
    // clear any prev animation
        span.classList.remove('pulse');     
    span.classList.remove('pulse_invert');
    
    // add/re-add animation as needed
    if (`sp${sp}` in pulseNormal) {             
        span.classList.add('pulse');
    } else if (`sp${sp}` in pulseInvert) {
        span.classList.add('pulse_invert');
    }
  }

}

【问题讨论】:

  • 我看到的最佳答案是这个(也许可以解决你的问题):stackoverflow.com/a/69612018/17716837
  • 谢谢@LaaouatniAnas,我已经尝试将其移植到上面的jsfiddle中并更改了所有名称以适应,但似乎并不能阻止动画在禁用然后重新同步后不同步在多个字母上启用相同的动画

标签: javascript css


【解决方案1】:

为了使它们同步,动画应该同时初始化。

这里我为span指定了animation-name,动画在定义其他道具之前不会运行(实际上我很好奇不同的渲染引擎是否共享相同的行为)。

let pulseList = {};

function toggleFx(spanID) {
  let spanTarget = document.getElementById(spanID);

  // toggle setting
  if (spanID in pulseList) {
    delete pulseList(spanID)
  } else {
    pulseList[spanID] = true;
  }

  // update display
  for (let sp of 'ABCD') {
    let span = document.getElementById(`sp${sp}`);
    span.classList.remove('pulse'); // clear any prev animation
    if (`sp${sp}` in pulseList) { // add/re-add animation as needed
      span.classList.add('pulse');
    }
  }
}
div {
  display: flex;
  flex-flow: column;
}

span {
  animation-name: pulse_active;
}

span.pulse {
  color: #f00;
  animation: pulse_active 1.5s ease-in infinite;
}

@keyframes pulse_active {
  0% {
    opacity: 0
  }
  50% {
    opacity: 0.66
  }
  100% {
    opacity: 0
  }
}
<div>
  <span id="spA" onclick="toggleFx('spA')">A</span>
  <span id="spB" onclick="toggleFx('spB')">B</span>
  <span id="spC" onclick="toggleFx('spC')">C</span>
  <span id="spD" onclick="toggleFx('spD')">D</span>
</div>

编辑

至少 Blink 和 Gecko 确实有相同的行为,我没有在 WebKit Safari 上进行测试。

【讨论】:

  • 感谢@Leo,这让我走到了我需要去的地方!我将最初的问题保留在单个动画上以保持帖子的简单性,但没有考虑到答案可能适用于单个动画,但不适用于将不同动画应用、删除和重新应用到元素的情况,具体取决于关于情况。我已经更新了 OP 和 jsfiddle 以包含第二个动画和更好的问题描述 - 有没有办法扩展这个解决方案以根据更新描述的需要工作?还是仅当元素不会更改动画时此解决方案才适用?
  • @1337ingDisorder,我觉得你需要更新jsfiddle链接,好像坏了。
  • 谢谢,用正确的链接编辑
  • 看一下新的例子,每次我们设置一个新的animation-name(或只是animation)时间都会重置,那么纯CSS解决方案将无法工作,即使是JS解决方案也可能令人头疼并可能会损害性能(例如:使用 requestAnimationFrame 设置动画以设置 opacity 值,以便您可以将其应用于循环中的所需元素)。
【解决方案2】:

为此,document.getAnimations() 是一个有用的方法。它返回当前生效的所有动画对象的数组,其目标元素是文档的后代。更多信息:https://developer.mozilla.org/en-US/docs/Web/API/Document/getAnimations

document.getAnimations() 的项目包括currentTime 属性,可用于同步动画。更多信息:https://drafts.csswg.org/web-animations/#dom-documentorshadowroot-getanimations

toggleFx函数被修改,现在分为三个部分。

在第一部分中,搜索document.getAnimations() 并找到currentTime 属性,其中animationNamepulse_activepulse_inverted 之一;然后将它们存储在相关变量中(即pulseActiveStartpulseInvertedStart)。

在第二部分中,spanTarget 中的className 如你所说发生了变化。

在第三部分,pulseActiveStartpulseInvertedStart 应用于相关动画的currentTime 属性(反向第一部分)。

function toggleFx(spanID) {

    let spanTarget = document.getElementById(spanID);
    
    let  pulseActiveStart;
    let  pulseInvertedStart;
    let anims = document.getAnimations()    
    for(let i = 0; i < anims.length; i++) { 
        if(anims[i].animationName == "pulse_active") pulseActiveStart = anims[i].currentTime;
        else if(anims[i].animationName == "pulse_inverted") pulseInvertedStart = anims[i].currentTime;      
    }
    
    
    
    if(spanTarget.classList.contains('pulse_invert')) {
        spanTarget.classList.remove('pulse_invert');    
        spanTarget.classList.remove('pulse');   
    } else if(spanTarget.classList.contains('pulse')) {
        spanTarget.classList.add('pulse_invert');   
        spanTarget.classList.remove('pulse');   
    } else {
        spanTarget.classList.add('pulse');  
    }
    
    anims = document.getAnimations()    
    for(let i = 0; i < anims.length; i++) { 
        if(anims[i].animationName == "pulse_active") {
            if(pulseActiveStart) anims[i].currentTime = pulseActiveStart;           
        } else if(anims[i].animationName == "pulse_inverted") {
            if(pulseInvertedStart) anims[i].currentTime = pulseInvertedStart;       
        }
    }
}
div {
  display: flex;
  flex-flow: column;
}

span.pulse {
    color: #f00;
    animation: pulse_active 1.5s ease-in infinite;
}

span.pulse_invert {
  color: #00f;
  animation: pulse_inverted 3s ease-in infinite;
}

@keyframes pulse_active {
    0% { opacity: 0; }
    50% { opacity: 0.66; }
    100% {  opacity: 0; }
}

@keyframes pulse_inverted {
    0% { opacity: 1; }
    50% { opacity: 0.33; }
    100% {  opacity: 1; }
}
<div>
  <span id="spA" onclick="toggleFx('spA')">A</span>
  <span id="spB" onclick="toggleFx('spB')">B</span>
  <span id="spC" onclick="toggleFx('spC')">C</span>
  <span id="spD" onclick="toggleFx('spD')">D</span>
</div>

【讨论】:

    【解决方案3】:

    我认为同步 CSS 动画是一个相当广泛的话题,不能用包罗万象的解决方案轻松解决。

    在这种特定情况下,最直接的方法可能是跟踪自首次添加 pulse 类以来的时间,然后延迟所有后续 pulse 类添加以等待下一次动画迭代的开始。

    请参阅modified jsfiddle 并提供建议的解决方案。它还会稍微调整样式以在等待下一次迭代时实现更平滑的过渡。希望它接近您想要实现的目标。

    理论上可以使用基准时间偏移来“计算”,并在添加pulse 类之前设置第一轮动画更精确的延迟,以实现更平滑的过渡。但这也需要更多的数学运算,并且肯定会在某种程度上使代码混乱。取决于您的目标和偏好。

    如果有人知道更简洁的解决方案,也许是纯 CSS 方法(?),那就太好了。

    【讨论】:

      【解决方案4】:

      根据@Amirreza Noori 的回答中的信息,我设法用简洁的小单线完成了这项工作:

      for (let a of document.getAnimations()) { a.startTime = 0 }
      

      无需查找现有动画的位置或区分特定动画 - 这只是重新启动页面上的所有动画。

      如果将来我引入在页面的其余动画重置时无法中断的动画,该答案中的其他信息(以及其他信息)将很有帮助。但是,如果我可以避免这样做,那么这种单线似乎可以很好地切芥末。

      感谢大家的回答和见解!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-01-22
        • 1970-01-01
        • 1970-01-01
        • 2021-12-01
        • 2019-11-26
        • 1970-01-01
        • 2013-04-09
        相关资源
        最近更新 更多