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


![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Заставить 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). Но если вы читаете значение позиции между изменениями, переход будет иметь новое значение.
Вместо того, чтобы заставлять 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 Почему это решение только для CSS? ... Стиль задается скриптом
@Teemu Я обновил свой ответ, чтобы было более понятно, как это сделать.
Я не думаю, что перейду на использование animation для этого, хотя он, безусловно, очень мощный, и его стоит использовать для некоторых вещей.
@spacerGIF Принудительная перерисовка DOM по мере необходимости только для transition стоит того с точки зрения производительности, поскольку это повлияет на общее впечатление пользователя. Кроме того, animation предназначен для этого, а transition - нет.
@spacerGIF И вы можете все настроить и позволить animation делать то, что он умеет лучше всего, вместо того, чтобы заполнять среду таймерами.
@LGSon Как мне сохранить синхронизацию двух анимаций с помощью вашего метода? Я использую setInterval для случайной генерации содержимого анимации, которое также необходимо синхронизировать. В этом случае перемещение вещей с использованием transition и JavaScript кажется способом вернуться назад, хотя, очевидно, это не единственное решение.
@spacerGIF Вы создаете и запускаете две анимации, даете второй задержку, соответствующую первой продолжительности, и они будут синхронизироваться намного лучше, чем вы можете с setInterval.
@spacerGIF В итоге, если у вас есть опыт, который лучше, сделайте свой выбор, хотя есть причина, по которой вам нужно использовать такие трюки, как перерисовка DOM, чтобы заставить transition работать. transition лучше всего подходит для интерактивных эффектов, таких как наведение и т. д., А animation - для эффектов, основанных на времени.
@LGson Две анимации, скорее всего, со временем рассинхронизируются. setInterval, даже если он не будет работать как точное измерение времени или чего-то еще, не будет иметь никакого дрейфа между ними.
@spacerGIF Я использовал еще несколько анимаций для решения цифровых вывесок, и у меня никогда не было проблем с дрейфом, но сейчас мы можем отказаться от этого обсуждения, я просто пытался поделиться своим опытом.
@LGSon Ну, может, мне стоит попробовать использовать для этого animation вместо transition. Между вашим решением и Teemu у меня нет разницы в производительности, поэтому для конечного пользователя не будет иметь значения, какой метод используется, и для меня, добавление одной строки (две в моей реальной анимации) является более простым решением. Не обижайтесь на мой выбор решения - оба работают одинаково хорошо. Но да, я не хочу спорить по этому поводу.
@spacerGIF Я не обиделась :) ... но когда я вижу, что кто-то сходит с ума, я могу настойчиво заставлять их рассматривать второй вариант, по крайней мере, когда я сам был там.
Мой сниппет не перерисовывается, а только рассчитывает. Кроме того, переходные элементы не попадают в текстовый поток, пересчет позиции - это легкая задача для браузеров, равно как и рендеринг страницы после выполнения скрипта.
@Teemu и spacer GIF, происходит изменение стиля, о котором вы можете прочитать здесь: stackoverflow.com/questions/3485365/… ... и здесь: gist.github.com/paulirish/5d52fb081b3570c81e3a
Да, то, что я пытался объяснить в своем ответе, во многом совпадает с тем, что пишет Пол Айриш. Перерасчет, являющийся узким местом в конкретном случае OP, является спорным, поскольку все элементы, подвергшиеся изменению стиля, находятся вне текстового потока. Важная часть заключается в том, что пересчет происходит только при чтении значений после изменения, не при установке их. Перерасчет будет выполнен в любом случае, когда сценарий будет завершен (при условии, что в DOM внесены изменения), и этот процесс оптимизирован для элементов, установленных из текстового потока, так что весь макет не нужно рассчитывать или даже повторно отображать.
@spacerGIF Я обновил оба образца, указав счетчик итераций и прослушиватель событий, который сработает, когда анимация будет завершена. Это сделает все событие установки управляемым, что является предпочтительным способом в отличие от опроса, и покажет, как, например, одна анимация может синхронизироваться с другой. Надеюсь, мой ответ хорош и достаточно полезен, чтобы получить положительный голос.
@LGSon Хорошо, хорошо, вы отметили вопрос как повторяющийся. Возьмите свой голос и перестаньте спорить о том, почему это лучший способ сделать это.
@spacerGIF Я провожу свободное время, чтобы помочь вам (и всем другим пользователям, которые нашли этот пост), добавляя дополнительный код, чтобы показать, как воспользоваться преимуществами функции animation, и вы называете это "препирательство" ?. И обман - это не плохо, это хорошо, когда пользователи получают ссылки на больше сообщений, что является одной из замечательных особенностей этого сайта.
@spacerGIF И если чистой причиной вашего голоса был мой "препирательство", пожалуйста, удалите его.
@LGSon Хорошо. Что касается споров, я имел в виду вас в связи с ответом Теему. Я ценю помощь, как и люди, которые сталкиваются с этим вопросом, но в данном случае я решил не использовать animation, хотя это определенно полезный вариант, особенно для более сложных вещей. И да, ответы, связанные с дубликатом, на самом деле очень полезны - извините, я немного поспешил с этим.
@spacerGIF Все отлично :)
Это простое решение, кажется, действительно хорошо работает. Спасибо, что отредактировали свой ответ и добавили объяснение, почему это работает!