Как прервать текущую анимацию CSS и запустить другую?

У меня есть следующий пример кода, который создает тосты, которые вылетают из верхней части экрана, остаются на несколько секунд, а затем улетают:

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>

Это прекрасно работает, но иногда в моем приложении будет создано другое всплывающее уведомление до того, как текущее исчезнет. В этом случае я хочу, чтобы текущее всплывающее уведомление улетало сразу после создания нового всплывающего уведомления, а не ждало 2,5 секунды.

Для жизни меня, я не могу понять это. Является ли это возможным? Это кажется таким простым, но ничто из того, что я пытаюсь сделать, не дает никакого эффекта.

Сначала я попытался захватить существующий элемент тоста и установить animation-delay на 0s, но это не дало никакого эффекта. Затем я попытался заменить его совершенно новой анимацией, попытался использовать !important и т. д. Во всех случаях тост просто исчезает, а анимация не происходит.

Есть ли способ просто заставить всплывающее уведомление немедленно закончить свою текущую анимацию примерно за 0,4 секунды при создании нового всплывающего уведомления? Чтобы он улетел до того, как столкнется с прилетающим тостом?

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
0
76
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Вам нужно сохранить ссылку на 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);

Но если вы хотите отслеживать саму анимацию, вы можете использовать некоторые события анимации.

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

О, у меня нет проблем с удалением элемента из DOM. То, что я пытаюсь сделать, это пропустить 2,5-секундную задержку анимации, когда быстро появляется другой тост.

Ryan Peschel 31.03.2023 14:27

отредактировал мой ответ. Я думаю, вы ищете анимационные события

SirOneOfMany 31.03.2023 14:29

Вы думаете, что это возможно, верно? Возможно, вам нужно добавить переменную, чтобы проверить, отображается ли тост или нет. Если отображается всплывающее уведомление, вы можете сразу же применить 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>

Не уверен, что это прямо отвечает на ваш вопрос, но вы можете выделить анимацию в отдельный класс... тогда с помощью 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(() => 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>
Ответ принят как подходящий

Я внес некоторые коррективы в ваш подход:

  1. Вместо того, чтобы назначать обе анимации одновременно, назначьте анимацию toastout только определенному классу, скажем, .out.

  2. При попытке удалить тост вызовите функцию removeToast(). Это добавляет класс .out к этому тосту. Таким образом, вы можете контролировать, когда воспроизводить анимацию toastout, а затем после воспроизведения анимации удалить элемент с помощью события animationend.

  3. Каждый раз, когда добавляется новый тост, попробуйте вызвать removeToast() на каждом существующем тосте.

Вот полное решение:

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>

О, это гениально! Спасибо! Это выглядит великолепно и содержит много хороших идей, которые значительно улучшат качество моего подхода.

Ryan Peschel 31.03.2023 14:36

Другие вопросы по теме