Изменение стиля HTML-элемента в JavaScript с временно отключенным переходом CSS работает ненадежно

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

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

В любом случае, перемещение обрабатывается с помощью свойства CSS переход, установленного таким образом, чтобы браузер мог сделать для этого тяжелую работу. Затем этот переход должен быть отменен, чтобы мгновенно сбросить положение элемента. Очевидный способ сделать это - сделать это, а затем повторно применить переход, когда ему нужно снова начать движение, что тоже происходит сразу же. Однако это не работает. Не совсем. Я объясню.

Взгляните на JavaScript в конце этого вопроса или в связанном JSFiddle, и вы увидите, что я это делаю, но setTimeout добавляет задержку в 25 мс между ними. Причина этого (и, вероятно, лучше всего попробовать это самостоятельно), если нет задержки (что я хочу) или очень короткая задержка, элемент будет либо периодически, либо постоянно оставаться на месте, что не является желаемый эффект. Чем выше задержка, тем больше вероятность того, что это сработает, хотя в моей реальной анимации это вызывает незначительное дрожание, потому что анимация состоит из двух частей и не рассчитана на задержку.

Это действительно похоже на то, что может быть ошибкой браузера, но я тестировал это в Chrome, Firefox 52 и текущей версии Firefox, все с аналогичными результатами. Я не уверен, что делать дальше, так как мне не удалось найти нигде об этой проблеме или каких-либо решений / обходных путей. Было бы очень признательно, если бы кто-то мог найти способ заставить это надежно работать, как задумано. :)


Вот страница JSFiddle с примером того, что я имею в виду.

Разметка и код также вставлены сюда:

var box = document.getElementById("box");
//Reduce this value or set it to 0 (I
//want rid of the timeout altogether)
//and it will only function correctly
//intermittently.
var delay = 25;

setInterval(function() {
  box.style.transition = "none";
  box.style.left = "1em";

  setTimeout(function() {
    box.style.transition = "1s linear";
    box.style.left = "11em";
  }, delay);
}, 1000);
#box {
  width: 5em;
  height: 5em;
  background-color: cyan;
  position: absolute;
  top: 1em;
  left: 1em;
}
<div id = "box"></div>
Поведение ключевого слова "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) для оценки ваших знаний,...
2
0
388
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Заставить DOM пересчитать себя перед установкой нового перехода после сброса. Этого можно достичь, например, прочитав смещение поля, примерно так:

var box = document.getElementById("box");

setInterval(function(){
      box.style.transition = "none";
      box.style.left = "1em";
      let x = box.offsetLeft; // Reading a positioning value forces DOM to recalculate all the positions after changes
      box.style.transition = "1s linear";
      box.style.left = "11em";  
    }, 1000);
body {
  background-color: rgba(0,0,0,0);
}

#box {
  width: 5em;
  height: 5em;
  background-color: cyan;
  position: absolute;
  top: 1em;
  left: 1em;
}
<div id = "box"></div>

См. Также рабочую демонстрацию на jsFiddle.

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

Что происходит без тайм-аута (и чтения свойств), так это то, что значение style.left сначала изменяется на 1em, а затем сразу на 11em. Переход происходит после того, как скрипт будет завершен, и увидит последнее установленное значение (11em). Но если вы читаете значение позиции между изменениями, переход будет иметь новое значение.

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

spacer GIF 14.03.2018 21:50

Вместо того, чтобы заставлять transition вести себя как анимацию, используйте animation, он будет работать намного лучше, что наиболее важно с точки зрения производительности, и для его просмотра не потребуется таймер.

С помощью событий animation можно синхронизировать анимацию любым подходящим способом, включая запуск таймера для ее перезапуска или изменения.

Либо с настройкой некоторых частей с помощью CSS

var box = document.getElementById("box");
box.style.left = "11em";    // start

box.addEventListener("animationend", animation_ended, false);

function animation_ended (e) {
  if (e.type == 'animationend') {
    this.style.left = "1em";
  }
}
#box {
  width: 5em;
  height: 5em;
  background-color: cyan;
  position: absolute;
  top: 1em;
  left: 1em;
  animation: move_me 1s linear 4;
}
@keyframes move_me {
  0% { left: 1em; }
}
<div id = "box"></div>

Или полностью на основе сценария

var prop = 'left', value1 = '1em', value2 = '11em'; 

var s = document.createElement('style');
s.type = 'text/css';
s.innerHTML = '@keyframes move_me {0% { ' + prop + ':' + value1 +' }}';
document.getElementsByTagName('head')[0].appendChild(s);

var box = document.getElementById("box");
box.style.animation = 'move_me 1s linear 4';
box.style.left = value2;      // start

box.addEventListener("animationend", animation_ended, false);

function animation_ended (e) {
  if (e.type == 'animationend') {
    this.style.left = value1;
  }
}
#box {
  width: 5em;
  height: 5em;
  background-color: cyan;
  position: absolute;
  top: 1em;
  left: 1em;
}
<div id = "box"></div>

Это определенно выглядит лучше.

Teemu 14.03.2018 21:22

@Teemu Почему это решение только для CSS? ... Стиль задается скриптом

Ason 14.03.2018 21:23

@Teemu Я обновил свой ответ, чтобы было более понятно, как это сделать.

Ason 14.03.2018 21:33

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

spacer GIF 14.03.2018 21:53

@spacerGIF Принудительная перерисовка DOM по мере необходимости только для transition стоит того с точки зрения производительности, поскольку это повлияет на общее впечатление пользователя. Кроме того, animation предназначен для этого, а transition - нет.

Ason 14.03.2018 21:56

@spacerGIF И вы можете все настроить и позволить animation делать то, что он умеет лучше всего, вместо того, чтобы заполнять среду таймерами.

Ason 14.03.2018 22:01

@LGSon Как мне сохранить синхронизацию двух анимаций с помощью вашего метода? Я использую setInterval для случайной генерации содержимого анимации, которое также необходимо синхронизировать. В этом случае перемещение вещей с использованием transition и JavaScript кажется способом вернуться назад, хотя, очевидно, это не единственное решение.

spacer GIF 14.03.2018 22:06

@spacerGIF Вы создаете и запускаете две анимации, даете второй задержку, соответствующую первой продолжительности, и они будут синхронизироваться намного лучше, чем вы можете с setInterval.

Ason 14.03.2018 22:13

@spacerGIF В итоге, если у вас есть опыт, который лучше, сделайте свой выбор, хотя есть причина, по которой вам нужно использовать такие трюки, как перерисовка DOM, чтобы заставить transition работать. transition лучше всего подходит для интерактивных эффектов, таких как наведение и т. д., А animation - для эффектов, основанных на времени.

Ason 14.03.2018 22:17

@LGson Две анимации, скорее всего, со временем рассинхронизируются. setInterval, даже если он не будет работать как точное измерение времени или чего-то еще, не будет иметь никакого дрейфа между ними.

spacer GIF 14.03.2018 22:23

@spacerGIF Я использовал еще несколько анимаций для решения цифровых вывесок, и у меня никогда не было проблем с дрейфом, но сейчас мы можем отказаться от этого обсуждения, я просто пытался поделиться своим опытом.

Ason 14.03.2018 22:39

@LGSon Ну, может, мне стоит попробовать использовать для этого animation вместо transition. Между вашим решением и Teemu у меня нет разницы в производительности, поэтому для конечного пользователя не будет иметь значения, какой метод используется, и для меня, добавление одной строки (две в моей реальной анимации) является более простым решением. Не обижайтесь на мой выбор решения - оба работают одинаково хорошо. Но да, я не хочу спорить по этому поводу.

spacer GIF 14.03.2018 22:42

@spacerGIF Я не обиделась :) ... но когда я вижу, что кто-то сходит с ума, я могу настойчиво заставлять их рассматривать второй вариант, по крайней мере, когда я сам был там.

Ason 14.03.2018 22:47

Мой сниппет не перерисовывается, а только рассчитывает. Кроме того, переходные элементы не попадают в текстовый поток, пересчет позиции - это легкая задача для браузеров, равно как и рендеринг страницы после выполнения скрипта.

Teemu 14.03.2018 22:49

@Teemu и spacer GIF, происходит изменение стиля, о котором вы можете прочитать здесь: stackoverflow.com/questions/3485365/… ... и здесь: gist.github.com/paulirish/5d52fb081b3570c81e3a

Ason 15.03.2018 06:38

Да, то, что я пытался объяснить в своем ответе, во многом совпадает с тем, что пишет Пол Айриш. Перерасчет, являющийся узким местом в конкретном случае OP, является спорным, поскольку все элементы, подвергшиеся изменению стиля, находятся вне текстового потока. Важная часть заключается в том, что пересчет происходит только при чтении значений после изменения, не при установке их. Перерасчет будет выполнен в любом случае, когда сценарий будет завершен (при условии, что в DOM внесены изменения), и этот процесс оптимизирован для элементов, установленных из текстового потока, так что весь макет не нужно рассчитывать или даже повторно отображать.

Teemu 15.03.2018 07:19

@spacerGIF Я обновил оба образца, указав счетчик итераций и прослушиватель событий, который сработает, когда анимация будет завершена. Это сделает все событие установки управляемым, что является предпочтительным способом в отличие от опроса, и покажет, как, например, одна анимация может синхронизироваться с другой. Надеюсь, мой ответ хорош и достаточно полезен, чтобы получить положительный голос.

Ason 15.03.2018 10:37

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

spacer GIF 15.03.2018 11:58

@spacerGIF Я провожу свободное время, чтобы помочь вам (и всем другим пользователям, которые нашли этот пост), добавляя дополнительный код, чтобы показать, как воспользоваться преимуществами функции animation, и вы называете это "препирательство" ?. И обман - это не плохо, это хорошо, когда пользователи получают ссылки на больше сообщений, что является одной из замечательных особенностей этого сайта.

Ason 15.03.2018 12:10

@spacerGIF И если чистой причиной вашего голоса был мой "препирательство", пожалуйста, удалите его.

Ason 15.03.2018 12:11

@LGSon Хорошо. Что касается споров, я имел в виду вас в связи с ответом Теему. Я ценю помощь, как и люди, которые сталкиваются с этим вопросом, но в данном случае я решил не использовать animation, хотя это определенно полезный вариант, особенно для более сложных вещей. И да, ответы, связанные с дубликатом, на самом деле очень полезны - извините, я немного поспешил с этим.

spacer GIF 15.03.2018 15:07

@spacerGIF Все отлично :)

Ason 15.03.2018 15:31

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