Приостановить / возобновить анимацию CSS при переключении вкладок

У меня есть куча длительных CSS-анимаций на странице. Я хочу приостановить их, когда пользователь переключается на другую вкладку, и возобновить их, когда пользователь снова вернется на исходную вкладку. Для простоты я пока не стремлюсь к кроссбраузерному решению; заставить его работать в Chrome должно быть достаточно.

document.addEventListener("visibilitychange", function() {
  if (document.hidden) {
    document.querySelector("#test").classList.add("paused");
  } else {
    document.querySelector("#test").classList.remove("paused");    
  }
});
div#test {
  width: 50px;
  height: 50px;
  background-color: red;
  position: absolute;
  left: 10vw;
  animation-name: move;
  animation-duration: 5s;
  animation-fill-mode: forwards;
  animation-timing-function: linear;
}

@keyframes move {
  to {
    left: 90vw
  }
}

.paused {
  animation-play-state: paused !important;
  -webkit-animation-play-state: paused !important;
  -moz-animation-play-state: paused !important;
}
<div id = "test"></div>

Приведенный выше код перемещает красный прямоугольник по горизонтали. Для завершения анимации прямоугольнику требуется 5 секунд.

Эта проблема: после запуска анимации перейти на другую вкладку браузера; через некоторое время (более 5 секунд) вернитесь к первой вкладке.

Ожидаемый результат: прямоугольник продолжает свой путь от точки, где он остановился.

Фактический результат: в большинстве случаев прямоугольник появляется в конечном пункте назначения и останавливается. Иногда это работает как положено. Видео демонстрирует проблему.

Я играл с разными значениями для animation-fill-mode и animation-timing-function, но результат всегда был одинаковым. Как и rv7 указал, совместное использование примеров в CodePen, JSFiddle, JSBin и JS-инструменте stackoverflow влияет на результаты, поэтому лучше тестировать непосредственно на статической странице на локальном HTTP-сервере (или используя ссылки ниже).

Для удобства я развернул код выше для Heroku. Приложение представляет собой статический HTTP-сервер nodejs, который работает по бесплатной подписке, поэтому первая загрузка страницы может занять до 5 минут. Результаты тестирования на этой странице:

FAIL Chrome 64.0.3282.167 (Official Build) (64-bit) Linux Debian Stretch (PC)
FAIL Chrome 70.0.3538.67 (Official Build) (64-bit) Windows 10 (PC)
FAIL Chrome 70.0.3538.77 (Official Build) (32-bit) Windows 7 (PC)
FAIL Chrome 70.0.3538.77 (Official Build) (64-bit) OSX 10.13.5 (Mac mini)
FAIL FF Quantum 60.2.2esr (64-bit) Linux Debian Stretch (PC)
OK Safari 11.1.1 (13605.2.8) (Mac mini)

API видимости страницы может быть заменен оконными событиями focus и blur следующим образом:

window.addEventListener("focus", function() {
    document.querySelector("#test").classList.remove("paused");        
});

window.addEventListener("blur", function() {
    document.querySelector("#test").classList.add("paused");
});

Однако эта замена не эквивалентна. Если страница содержит IFRAME, взаимодействие с их содержимым вызовет события focus и blur в главном окне. Выполнение любого кода переключения вкладок в этом случае некорректно. В некоторых случаях это все еще может быть вариантом, поэтому я развернул страницу для тестирования здесь. Результаты немного лучше:

FAIL Chrome 64.0.3282.167 (Official Build) (64-bit) Linux Debian Stretch (PC)
FAIL Chrome 70.0.3538.67 (Official Build) (64-bit) Windows 10 (PC)
FAIL Chrome 70.0.3538.77 (Official Build) (32-bit) Windows 7 (PC)
FAIL Chrome 70.0.3538.77 (Official Build) (64-bit) OSX 10.13.5 (Mac mini)
OK FF Quantum 60.2.2esr (64-bit) Linux Debian Stretch (PC)
OK Safari 11.1.1 (13605.2.8) (Mac mini)
This version fails more often in Chrome 64-bit than in Chrome 32-bit. In Chrome 32-bit I got just 1 failure after ~20 successful attempts. This might be related to the fact that Chrome 32-bit is installed on older hardware.

Вопрос: есть ли способ надежно приостанавливать / возобновлять анимацию CSS при переключении вкладок?

Эта проблема сохраняется до сих пор, хотя поддержка css теперь поддерживается всеми основными браузерами. Пробовал с фокусом / размытием, работает немного лучше, но тоже не работает. Я отказался от попыток поставить его на паузу и просто решил начать с самого начала при размытии, добавив animation: none;

Gabriel Linassi 06.08.2021 05:30
Поведение ключевого слова "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) для оценки ваших знаний,...
21
1
3 594
3

Ответы 3

Для меня ваш пример работает большую часть времени.

Однако я бы использовал проценты, а также добавил бы начальную точку в ключевые кадры:

@keyframes move {
  0% { 
    left: 0px;
  }
  100% {
    left: 500px
  }
}

Также обнаружение неактивной вкладки срабатывает не всегда. Неактивное окно работает лучше: (с jQuery)

$(window).on("blur focus", function(e) {
    var prevType = $(this).data("prevType");

    if (prevType != e.type) {
        var element = document.getElementById("test");
        switch (e.type) {
            case "blur":
                element.style["animation-play-state"] = "paused";
                break;
            case "focus":
                element.style["animation-play-state"] = "running";
                break;
        }
    }

    $(this).data("prevType", e.type);
})

Я удалил отправную точку для простоты, в моем реальном сценарии она есть. Добавление / удаление ничего не меняет. Я попытался разместить console.info внутри обработчика переключения вкладок и могу подтвердить, что он всегда запускается.

eugenesqr 22.10.2018 09:16

В iframe это не работает, но когда это фактический документ, он работает: пример. jobse.be/animation.html

Jobse 22.10.2018 13:28

@Jobse ваше решение сложнее проверить, так как анимация зацикливается. В любом случае, похоже, он работает в FF, но не в Chrome.

eugenesqr 22.10.2018 14:01

Странно, я проверил в IE / FF / Chrome

Jobse 22.10.2018 14:27

Вы можете использовать focus и blur события вместо visibilitychange событие из-за лучшей поддержки браузером первого!

let box = document.querySelector('#test');

window.addEventListener('focus', function() {
  box.style.animationPlayState = 'paused';
});

window.addEventListener('blur', function() {
  box.style.animationPlayState = 'running';
});

В качестве альтернативы вы также можете сделать это с помощью классов CSS:

.paused {
  animation-play-state: paused !important;
  -webkit-animation-play-state: paused !important;
  -moz-animation-play-state: paused !important;
}
let box = document.querySelector('#test');

window.addEventListener('focus', function() {
  box.classList.remove('paused');
});

window.addEventListener('blur', function() {
  box.classList.add('paused');
});

Указанные выше два способа не работают в iframe CodePen, JSFiddle, JSBin и т. Д .; возможная причина указана в конце сообщения. Но, вот ссылка на видео, показывающая, как код работает в режиме отладки CodePen.

Живой пример

Confirmed in:

  1. Google Chrome v70.0.3538.67 (Official Build) (32-bit)

  2. Firefox Quantum v62.0.3 (32-bit)

  3. Internet Explorer v11+


Возможная причина, по которой код не работает внутри iframe:

Когда я попытался получить доступ к root window (он же parent объект, обратите внимание, что это не iframe window), в консоли появилась следующая ошибка:

А это означает, что я не могу получить доступ к root window CodePen и других. Поэтому, если я не могу получить к нему доступ, я не могу добавить к нему никаких прослушивателей событий.

Вот видео, который иллюстрирует проблему с предложенным решением.
eugenesqr 24.10.2018 14:46

Почему использование jQuery здесь что-то меняет?

Kaiido 24.10.2018 16:19

@Kaiido, использование jQuery здесь мало что меняет. Вы также можете использовать эквивалент Javascript. Кстати, код работает за вас?

vrintle 24.10.2018 17:02

@ rv7 Мне все еще непонятно. Вы хотите сказать, что изначально опубликованный мной код работает только изредка, в то время как код с blur и focus работает у вас постоянно? Или обе версии всегда работают?

eugenesqr 24.10.2018 17:09

@Femel опубликованный вами код не работает для меня, в то время как код, который я опубликовал, работает для меня каждый раз.

vrintle 24.10.2018 17:23

@Femel Я добавил еще один способ с использованием классов CSS и Javascript. Проверьте, работает ли это для вас или нет. Кстати, у меня как обычно работало.

vrintle 24.10.2018 17:32

@ rv7 Нет, результат тот же. Я обновил свой вопрос новым кодом и снова все протестировал. Кстати, этот подход не то же самое, что использование API видимости страницы, когда в игру вступают IFRAME.

eugenesqr 24.10.2018 23:02

@Femel Приложение visibility-api не работает. Но приложение focus-blurсработало, как и ожидалось!

vrintle 25.10.2018 14:46

@ rv7, мне удалось протестировать оба приложения на моем ноутбуке 10-летней давности в последней 32-битной версии Chrome. Результаты очень похожи на ваши, за исключением того, что мне удалось вывести приложение focus-blur из строя всего один раз после почти 20 успешных попыток. Так что проблема возникает очень редко, но она определенно существует. Вы можете попробовать 64-битный Chrome?

eugenesqr 26.10.2018 09:12

@Femel Я согласен с тем, что код не работает один раз из 20 (или более) попыток. Но логически я не могу сказать, что это код, это могут быть какие-то ошибки во время выполнения или какие-то скрытые ошибки и т. д. Кроме того, я протестировал ваше приложение focus-blur на своем 64-битном ноутбуке, и результаты были такими же, как в 32-битном устройстве.

vrintle 26.10.2018 12:43

@Femel, твоя проблема все еще существует? Потому что я заинтересован в том, чтобы начать еще одну награду за это, если вы готовы к новым (возможно, лучшим) ответам.

vrintle 08.12.2018 16:19

@ rv7 проблема все еще существует. Я использую переходы CSS в качестве обходного пути, поэтому мне нет реальной необходимости решать эту проблему. Я могу провести небольшое тестирование, если появится какой-нибудь достойный ответ, но это все.

eugenesqr 11.12.2018 08:30

Вам не нужно использовать какие-либо классы или переключать их, чтобы получить ожидаемый результат. Здесь скачайте html-файл это суть и попробуйте со своей стороны, давайте посмотрим, работает ли это.

Вам необходимо установить animation-play-state при записи CSS для блока test в paused.

Как только окно загрузится, установите animation-play-state на running, затем снова установите их в соответствии с событиями blur и focus.

Также Я заметил, что использование префикса браузера (-webkit-animation-play-state) помогло мне получить ожидаемый результат, поэтому обязательно используйте их. Я тестировал firefox и chrome, у меня отлично работает.

Примечание: Это может не работать с этим фрагментом кода здесь (так как фрагмент не находится в главном окне этой текущей вкладки). Загрузите ссылку, указанную выше, и проверьте на своей стороне.

window.onload = function() {
  // not mentioning the state at all does not provide the expected result, so you need to set 
  // the state to paused and set it to running on window load
  document.getElementById('test').style.webkitAnimationPlayState = "running";
  document.getElementById('test').style.animationPlayState = 'running';
}

window.onblur = function() {
  document.title = 'paused now';
  document.getElementById('test').style.webkitAnimationPlayState = "paused";
  document.getElementById('test').style.animationPlayState = 'paused';
  document.getElementById('test').style.background = 'pink'; // for testing
}

window.onfocus = function() {
  document.title = 'running now';
  document.getElementById('test').style.webkitAnimationPlayState = "running";
  document.getElementById('test').style.animationPlayState = 'running';
  document.getElementById('test').style.background = 'red'; // for testing
}
div#test {
  width: 50px;
  height: 50px;
  background-color: red;
  position: absolute;
  left: 10vw;
  animation-name: move;
  animation-duration: 5s;
  animation-fill-mode: forwards;
  animation-timing-function: linear;
  /* add the state here and set it to running on window load */
  -webkit-animation-play-state: paused;
  animation-play-state: paused;
}

@keyframes move {
  to {
    left: 90vw
  }
}
<div id = "test"></div>

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