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:
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);
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>
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?
>Solution :
I made some adjustments to your approach:
-
Instead of assigning both of the animations at the same time, only assign the
toastoutanimation to a certain class, let’s say.out. -
When trying to remove a toast, call function
removeToast(). It adds class.outto that toast. So you can control when to play thetoastoutanimation, then after the animation is played, remove the element using eventanimationend. -
Each time a new toast is added, try to call
removeToast()on every existing toast.
Here’s the full solution:
function createToast() {
// remove all existing toasts
[...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 a toast is already removed, ignore
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()">Add toast</button>
<body>