如何中断当前的CSS动画并开始另一个?

huangapple go评论74阅读模式
英文:

How to interrupt current CSS animation and start another?

问题

以下是您要翻译的代码部分:

I have the following example code which creates toasts that fly out from the top of the screen, stay for a few seconds, and then fly away:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

    function createToast() {
      const elem = document.createElement("div");
      elem.classList.add("toast");
      elem.innerText = "hello";

      document.body.appendChild(elem);

      setTimeout(() => elem.remove(), 3000);
    }

    setInterval(createToast, 3000);

<!-- language: lang-css -->

    body {
      background-color: #d7d7d7;
    }

    .toast {
      display: flex;
      justify-content: center;
      align-items: center;
      padding: 5px 10px;
      max-width: 200px;
      transform: translate(0, 0);
      background-color: #fff;
      border-radius: 50px;

      animation-name: toastin, toastout;
      animation-duration: 0.3s, 0.5s;
      animation-iteration-count: 1, 1;
      animation-delay: 0s, 2.5s;
      animation-fill-mode: forwards, forwards;
    }

    @keyframes toastin {
      from {
        opacity: 0;
        transform: translate(0, -40px);
      }
      to {
        opacity: 1;
        transform: translate(0, 20px);
      }
    }

    @keyframes toastout {
      from {
        opacity: 1;
        transform: translate(0, 20px);
      }
      to {
        opacity: 0;
        transform: translate(50vw, 20px);
      }
    }

<!-- language: lang-html -->

    <body><body>

<!-- end snippet -->
英文:

I have the following example code which creates toasts that fly out from the top of the screen, stay for a few seconds, and then fly away:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

function createToast() {
  const elem = document.createElement(&quot;div&quot;);
  elem.classList.add(&quot;toast&quot;);
  elem.innerText = &quot;hello&quot;;

  document.body.appendChild(elem);

  setTimeout(() =&gt; elem.remove(), 3000);
}

setInterval(createToast, 3000);

<!-- language: lang-css -->

body {
  background-color: #d7d7d7;
}

.toast {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 5px 10px;
  max-width: 200px;
  transform: translate(0, 0);
  background-color: #fff;
  border-radius: 50px;

  animation-name: toastin, toastout;
  animation-duration: 0.3s, 0.5s;
  animation-iteration-count: 1, 1;
  animation-delay: 0s, 2.5s;
  animation-fill-mode: forwards, forwards;
}

@keyframes toastin {
  from {
    opacity: 0;
    transform: translate(0, -40px);
  }
  to {
    opacity: 1;
    transform: translate(0, 20px);
  }
}

@keyframes toastout {
  from {
    opacity: 1;
    transform: translate(0, 20px);
  }
  to {
    opacity: 0;
    transform: translate(50vw, 20px);
  }
}

<!-- language: lang-html -->

&lt;body&gt;&lt;body&gt;

<!-- end snippet -->

This works fine, but sometimes in my application another toast will be created before the current one is gone. In that case, I want the current toast to fly away immediately upon the creation of the new toast, instead of waiting 2.5 seconds.

For the life of me, I cannot figure this out. Is it possible? It seems so simple, but nothing I try seems to have any effect.

First I tried grabbing the existing toast element and setting the animation-delay to 0s, but that had no effect. Then I tried replacing it with an entire new animation, tried using !important, etc. In all cases the toast just, like, disappears, with no animation happening.

Is there any way to just make the toast immediately finish its current animation in like 0.4 seconds when a new toast is created? So that it flies away before it collides with the incoming toast?

答案1

得分: 1

我对你的方法进行了一些调整:

  1. 不要同时为两个动画分配类名,只将 toastout 动画分配给一个特定的类,比如 .out

  2. 当尝试移除提示时,调用 removeToast() 函数。它会给提示添加 .out 类,这样你可以控制何时播放 toastout 动画,然后在动画结束后使用 animationend 事件移除元素。

  3. 每次添加新提示时,尝试在所有现有提示上调用 removeToast()

以下是完整的解决方案:

function createToast() {
  // 移除所有现有的提示
  [...document.querySelectorAll('.toast')].forEach(removeToast);
  
  const elem = document.createElement("div");
  elem.classList.add("toast");
  elem.innerText = "hello";
  setTimeout(() => removeToast(elem), 3000);
  document.body.appendChild(elem);
}

function removeToast(toast) {
  // 如果提示已经被移除,忽略
  if (!toast || toast.classList.contains('out')) return;
  
  toast.classList.add('out');
  toast.addEventListener('animationend', () => toast.remove());
}
body {
  background-color: #d7d7d7;
}

.toast {
  display: flex;
  position: fixed;
  justify-content: center;
  align-items: center;
  padding: 5px 10px;
  max-width: 200px;
  transform: translate(0, 0);
  background-color: #fff;
  border-radius: 50px;

  animation-name: toastin;
  animation-duration: 0.3s;
  animation-iteration-count: 1;
  animation-fill-mode: forwards;
}

.toast.out {
  animation-name: toastout;
}

@keyframes toastin {
  from {
    opacity: 0;
    transform: translate(0, -40px);
  }
  to {
    opacity: 1;
    transform: translate(0, 20px);
  }
}

@keyframes toastout {
  from {
    opacity: 1;
    transform: translate(0, 20px);
  }
  to {
    opacity: 0;
    transform: translate(50vw, 20px);
  }
}
<body>
  <button onclick="createToast()">添加提示</button>
</body>

希望这能帮助你实现你的目标。

英文:

I made some adjustments to your approach:

  1. Instead of assigning both of the animations at the same time, only assign the toastout animation to a certain class, let's say .out.

  2. When trying to remove a toast, call function removeToast(). It adds class .out to that toast. So you can control when to play the toastout animation, then after the animation is played, remove the element using event animationend.

  3. Each time a new toast is added, try to call removeToast() on every existing toast.

Here's the full solution:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

function createToast() {
  // remove all existing toasts
  [...document.querySelectorAll(&#39;.toast&#39;)].forEach(removeToast);
  
  const elem = document.createElement(&quot;div&quot;);
  elem.classList.add(&quot;toast&quot;);
  elem.innerText = &quot;hello&quot;;
  setTimeout(() =&gt; removeToast(elem), 3000);
  document.body.appendChild(elem);
}

function removeToast(toast) {
  // if a toast is already removed, ignore
  if (!toast || toast.classList.contains(&#39;out&#39;)) return;
  
  toast.classList.add(&#39;out&#39;);
  toast.addEventListener(&#39;animationend&#39;, () =&gt; toast.remove());
}

<!-- language: lang-css -->

body {
  background-color: #d7d7d7;
}

.toast {
  display: flex;
  position: fixed;
  justify-content: center;
  align-items: center;
  padding: 5px 10px;
  max-width: 200px;
  transform: translate(0, 0);
  background-color: #fff;
  border-radius: 50px;

  animation-name: toastin;
  animation-duration: 0.3s;
  animation-iteration-count: 1;
  animation-fill-mode: forwards;
}

.toast.out {
  animation-name: toastout;
}

@keyframes toastin {
  from {
    opacity: 0;
    transform: translate(0, -40px);
  }
  to {
    opacity: 1;
    transform: translate(0, 20px);
  }
}

@keyframes toastout {
  from {
    opacity: 1;
    transform: translate(0, 20px);
  }
  to {
    opacity: 0;
    transform: translate(50vw, 20px);
  }
}

<!-- language: lang-html -->

&lt;body&gt;
  &lt;button onclick=&quot;createToast()&quot;&gt;Add toast&lt;/button&gt;
&lt;body&gt;

<!-- end snippet -->

答案2

得分: 0

您需要在函数再次调用时保留对"elem"的引用,并在函数内部将其移除。也许可以像这样实现:

let activeToast;

function clearToast() {
  if (!activeToast) {
    return;
  }
  activeToast.remove();
  activeToast = null;
}

function createToast() {
  clearToast();
  const elem = document.createElement("div");
  elem.classList.add("toast");
  elem.innerText = "hello";
  activeToast = elem;
  document.body.appendChild(elem);

  setTimeout(() => clearToast(), 3000);
}

setInterval(createToast, 3000);

但如果您想跟踪动画本身,您可能会希望使用一些动画事件,您可以参考这篇文章:

CSS Animation Events in JavaScript

英文:

You need to keep a reference to elem and remove it when the function is called again. Maybe something like this:

let activeToast;

function clearToast() {
  if(!activeToast){
     return;
  }
  activeToast.remove();
  activeToast = null;
}

function createToast() {
  clearToast();
  const elem = document.createElement(&quot;div&quot;);
  elem.classList.add(&quot;toast&quot;);
  elem.innerText = &quot;hello&quot;;
  activeToast = elem;
  document.body.appendChild(elem);

  setTimeout(() =&gt; clearToast(), 3000);
}

setInterval(createToast, 3000);

But if you want to keep track of the animation itself there are some animation events you might want to use.

https://medium.com/front-end-weekly/css-animation-events-in-javascript-d2bfe702636d

答案3

得分: 0

你会觉得这是可能的,对吗?也许你需要添加一个变量来检查是否有一个提示框正在显示。如果有提示框正在显示,你可以立即应用 elem.remove(),只显示新的提示框。请查看下面以查看是否适用于您。

let isToastDisplayed = false;

function createToast() {
  const elem = document.createElement("div");
  elem.classList.add("toast");
  elem.innerText = "hello";

  document.body.appendChild(elem);

  if (isToastDisplayed) {
    const currentToast = document.querySelector(".toast");
    currentToast.remove();
  }

  isToastDisplayed = true;

  setTimeout(() => {
    elem.remove();
    isToastDisplayed = false;
  }, 3000);
}

setInterval(createToast, 3000);
body {
  background-color: #d7d7d7;
}

.toast {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 5px 10px;
  max-width: 200px;
  transform: translate(0, 0);
  background-color: #fff;
  border-radius: 50px;

  animation-name: toastin, toastout;
  animation-duration: 0.3s, 0.5s;
  animation-iteration-count: 1, 1;
  animation-delay: 0s, 2.5s;
  animation-fill-mode: forwards, forwards;
}

@keyframes toastin {
  from {
    opacity: 0;
    transform: translate(0, -40px);
  }
  to {
    opacity: 1;
    transform: translate(0, 20px);
  }
}

@keyframes toastout {
  from {
    opacity: 1;
    transform: translate(0, 20px);
  }
  to {
    opacity: 0;
    transform: translate(50vw, 20px);
  }
}
<body></body>
英文:

You'd think it's possible, right? Maybe you need to add a variable to check whether there's a toast being displayed or not. If there's a toast being displayed, you can apply elem.remove() right away to only show the new toast. Check below to see if that works for you.

<!-- begin snippet: js hide: false console: true babel: null -->

<!-- language: lang-js -->

let isToastDisplayed = false;

function createToast() {
  const elem = document.createElement(&quot;div&quot;);
  elem.classList.add(&quot;toast&quot;);
  elem.innerText = &quot;hello&quot;;

  document.body.appendChild(elem);

  if (isToastDisplayed) {
    const currentToast = document.querySelector(&quot;.toast&quot;);
    currentToast.remove();
  }

  isToastDisplayed = true;

  setTimeout(() =&gt; {
    elem.remove();
    isToastDisplayed = false;
  }, 3000);
}

setInterval(createToast, 3000);

<!-- language: lang-css -->

body {
  background-color: #d7d7d7;
}

.toast {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 5px 10px;
  max-width: 200px;
  transform: translate(0, 0);
  background-color: #fff;
  border-radius: 50px;

  animation-name: toastin, toastout;
  animation-duration: 0.3s, 0.5s;
  animation-iteration-count: 1, 1;
  animation-delay: 0s, 2.5s;
  animation-fill-mode: forwards, forwards;
}

@keyframes toastin {
  from {
    opacity: 0;
    transform: translate(0, -40px);
  }
  to {
    opacity: 1;
    transform: translate(0, 20px);
  }
}

@keyframes toastout {
  from {
    opacity: 1;
    transform: translate(0, 20px);
  }
  to {
    opacity: 0;
    transform: translate(50vw, 20px);
  }
}

<!-- language: lang-html -->

&lt;body&gt;&lt;body&gt;

<!-- end snippet -->

答案4

得分: 0

不确定这是否直接回答了你的问题,但你可以尝试将动画分离到一个单独的类中...然后使用 JavaScript 检查现有的动画并添加该类!

检查一下:

function createToast() {
    const existing = document.querySelectorAll('.toast');
    if (existing.length > 0) {
        existing.forEach(e => {
            e.classList.add('toast-out');
        });
    }
    
    const elem = document.createElement("div");
    elem.classList.add("toast");
    elem.innerText = "hello";

    document.body.appendChild(elem);

    setTimeout(() => elem.remove(), 3000);
}

setInterval(createToast, 2000);
body {
    background-color: #d7d7d7;
}

.toast {
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 5px 10px;
    max-width: 200px;
    transform: translate(0, 0);
    background-color: #fff;
    border-radius: 50px;

    animation-name: toastin;
    animation-duration: 0.3s;
    animation-iteration-count: 1;
    animation-delay: 0s;
    animation-fill-mode: forwards;
}

.toast-out {
    animation-name: toastout;
    animation-duration: 0.5s;
    animation-iteration-count: 1;
    animation-delay: 0s;
    animation-fill-mode: forwards;
}

@keyframes toastin {
    from {
        opacity: 0;
        transform: translate(0, -40px);
    }
    to {
        opacity: 1;
        transform: translate(0, 20px);
    }
}

@keyframes toastout {
    from {
        opacity: 1;
        transform: translate(0, 20px);
    }
    to {
        opacity: 0;
        transform: translate(50vw, 20px);
    }
}
<body></body>

以上是你提供的代码的翻译部分。

英文:

Not sure if this directly answer your question but you might be able to separate the animation out on a separate class... then with js you can check for existing ones and add the class out!

check it out

<!-- begin snippet: js hide: false console: true babel: null -->

<!-- language: lang-js -->

function createToast() {

const existing = document.querySelectorAll('.toast')
if(existing.length > 0) {
existing.forEach(e => {
e.classList.add('toast-out')
})
}
const elem = document.createElement("div");
elem.classList.add("toast");
elem.innerText = "hello";

  document.body.appendChild(elem);

  setTimeout(() =&gt; elem.remove(), 3000);
}

setInterval(createToast, 2000);

<!-- language: lang-css -->

body {
  background-color: #d7d7d7;
}

.toast {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 5px 10px;
  max-width: 200px;
  transform: translate(0, 0);
  background-color: #fff;
  border-radius: 50px;

  animation-name: toastin;
  animation-duration: 0.3s;
  animation-iteration-count: 1;
  animation-delay: 0s;
  animation-fill-mode: forwards;
}

.toast-out {
animation-name: toastout;
animation-duration: 0.5s;
animation-iteration-count: 1;
animation-delay: 0s;
animation-fill-mode: forwards;
}

@keyframes toastin {
  from {
    opacity: 0;
    transform: translate(0, -40px);
  }
  to {
    opacity: 1;
    transform: translate(0, 20px);
  }
}

@keyframes toastout {
  from {
    opacity: 1;
    transform: translate(0, 20px);
  }
  to {
    opacity: 0;
    transform: translate(50vw, 20px);
  }
}

<!-- language: lang-html -->

&lt;body&gt;&lt;body&gt;

<!-- end snippet -->

huangapple
  • 本文由 发表于 2023年3月31日 20:13:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/75898420.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定