2017-02-14 11 views
3

Когда я хочу выполнить исполнение своего javascript и разрешить браузеру применять стили и т. Д., Прежде чем продолжить, я, как правило, использую общую технику setTimeout с задержкой 0, чтобы иметь обратный вызов, поставленный в очередь в конце цикла событий. Однако я столкнулся с ситуацией, когда это, похоже, не работает надежно.setTimeout (fn, 0) триггер перед стилями были применены

В приведенном ниже фрагменте есть класс active, применяющий переход к элементу chaser.

Когда я парить над целевой DIV, я хочу, чтобы удалить active класс от chaser элемента, переместите chaser на новое место, а затем повторно использовать active класс. Эффект должен заключаться в том, что o должен немедленно исчезнуть, а затем исчезнуть в новом месте. Вместо этого используются как opacity, так и top, поэтому oслайд с позиции на позицию, большую часть времени.

Если я увеличиваю задержку внутреннего таймаута до 10, он начинает вести себя так, как я и предполагал. Если я установил его на 5, то он иногда делает, а иногда нет.

Я бы ожидал, что любой setTimeout поставит в очередь мой обратный вызов до тех пор, пока не будут применены обновления стиля, но здесь есть четкое условие гонки. Я что-то упускаю? Есть ли способ гарантировать порядок обновления?

Я нахожусь на Chrome 56 на macOS и Windows, еще не проверял другие браузеры.

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

var targets = document.querySelectorAll('.target'); 
 
var chaser = document.querySelector('#chaser'); 
 
for (var i = 0; i < targets.length; i++) { 
 
    targets[i].addEventListener('mouseenter', function(event) { 
 
    chaser.className = ''; 
 
    setTimeout(function() { 
 
     // at this point, I'm expecting no transition 
 
     // to be active on the element 
 
     chaser.style.top = event.target.offsetTop + "px"; 
 
     
 
     setTimeout(function() { 
 
     // at this point, I'm expecting the element to 
 
     // have finished moving to its new position 
 

 
     chaser.className = 'active'; 
 
     }, 0); 
 
    }, 0); 
 
    }); 
 
}
#chaser { 
 
    position: absolute; 
 
    opacity: 0; 
 
} 
 
#chaser.active { 
 
    transition: all 1s; 
 
    opacity: 1; 
 
} 
 
.target { 
 
    height: 30px; 
 
    width: 30px; 
 
    margin: 10px; 
 
    background: #ddd; 
 
}
<div id="chaser">o</div> 
 
<div class="target">x</div> 
 
<div class="target">x</div> 
 
<div class="target">x</div> 
 
<div class="target">x</div> 
 
<div class="target">x</div>

+1

что, если вместо SetTimeout (Fn, 0) вы используете requestAnimationFrame. Если вы выполните двойной запросAnimationFrame, я бы подумал, что браузер должен быть обновлен. – David

+0

@ Давид может работать, не уверен, дает ли он какие-либо гарантии, но я кое-что прочитаю. Я больше беспокоюсь о том, что 'setTimeout' не делает то, что я ожидаю, хотя, как я полагался на него в прошлом, и теперь мне интересно, что еще может быть потенциально нарушено. – CupawnTae

+0

@ Давид начальных экспериментов с одним' requestAnimationFrame 'показывать это работает большую часть времени, но не всегда. Я думаю, что второй 'requestAnimationFrame', вероятно, достигнет согласованности, но тогда я думаю, что это, вероятно, только потому, что вы в основном задерживаете достаточно долго, чтобы избежать условия гонки. Мне бы очень хотелось, чтобы что-то детерминированное ... – CupawnTae

ответ

1

Что вам нужно слушать это transitionend событие, прежде чем делать что-нибудь еще. Вы можете читать на MDN about transitionend event. Btw, setTimeout никогда не должны использоваться для гарантии времени.

EDIT: Это для справки после разъяснения от ОП. Всякий раз, когда изменение стиля происходит с элементом, происходит либо перепланирование, либо перерисовка. Вы можете read more about them here. Если второй setTimeout запускается до первого пересчета, вы получаете эффект скольжения. Причина, по которой 10 мс приведет к желаемому эффекту, состоит в том, что класс .active добавляется после того, как свойство offsetTop было скорректировано (что приводит к transition свойствам, примененным после изменения элемента, это offsetTop). Обычно есть 60 кадров в секунду (например, ~ 16 мс на кадр), что означает, что у вас есть окно с 16 мс, чтобы сделать что-либо до применения новых стилей. Вот почему небольшая задержка в 5 мсек иногда приводит к разным результатам.

TL: DR - браузер запрашивает JS и CSS каждые 16 мс для любых обновлений и вычисляет, что делать. Если вы пропустите окно 16ms, вы можете иметь совершенно разные результаты.

+0

Спасибо, t хочу дождаться перехода - я хочу прервать его посередине, если он выполняется, что на самом деле отлично работает, это вторая часть, которая ведет себя странно, и в этом случае не должно быть перехода – CupawnTae

+0

Это потому, что у вас есть setTimeout внутри набораTimeout. Второй setTimeout будет добавлен только в цикл выполнения, только когда первый запустится и будет работать только после освобождения основного цикла JS. – Bwaxxlo

+0

, но это именно то, что я хочу делать. На самом деле, у меня изначально не было внешнего 'setTimeout' - я сделал класс remove и изменил' top' за один шаг, а затем setTimeout снова добавил класс 'active'. Я добавил дополнительный шаг на случай, если это вызовет проблему, но это не так. – CupawnTae

0

Вы вызываете метод setTimeout() в другом методе синхронизации setTimeout().

Моя мысль, почему бы не назвать метод как SetTimeout() отдельно так:

Первый SetTimeout() метод должен выполнить первый, а затем в конце выполнения, он должен вызвать второй SetTimeout() метод ,

+0

Я не понимаю, как ваше предложение отличается от того, что у меня есть, - второй 'setTimeout' происходит в конце выполнения обратного вызова для первого' setTimeout' – CupawnTae

+0

Ну, я пытаюсь сказать, что, вызывая оба setTimeout() независимо были бы предпочтительнее. –

+0

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

0

Вот рабочий скрипт для перемещения чэйзер:

function _(id) { return document.getElementById(id); } 

window.addEventListener('load', function() { 

var targets = document.querySelectorAll('.target'); 
    var chaser = document.querySelector('#chaser'); 

setTopPosition(targets); 

function setTopPosition(targets) { 
    for (var i = 0; i < targets.length; i++) { 
     targets[i].addEventListener('mouseenter', function(event) { 

      chaser.className = ''; 
      _('status').innerText = chaser.className; // to inspect the active class 

      setTimeout(function() { 
       /* at this point, I'm expecting no transition // to be active on the element */ 

       chaser.style.top = event.target.offsetTop + "px"; 
     }, 0); 

      // check if charser.className == '' 
      if (chaser.className == '') { 
       setClassName(); 
      } else { 
       alert(0); 
      } 

     }); // addEventListener 
    } // for 
} //function setTopPosition(targets) 

function setClassName() { 
    setTimeout(function() { 

     /* at this point, I'm expecting the element to have finished moving to its new position */ 

     chaser.className = 'active'; 
     _('status').innerText = chaser.className; 
     }, 0); 
} // function setClassName() 

}); 

HTML:

<div id="chaser">o</div> 

<div class="target">x</div> 
<div class="target">x</div> 
<div class="target">x</div> 
<div class="target">x</div> 
<div class="target">x</div> 

<div id="status">status</div> 

CSS:

#chaser { position: absolute; opacity: 0; } 
#chaser.active { transition: all 1s; opacity: 1; } 
.target { height: 30px; width: 30px; margin: 10px; background: #ddd; } 
+0

На самом деле я не думаю, что это отвечает на мой вопрос. Мне нужно больше времени, чтобы прочитать его. Просто заметьте: вы, вероятно, не хотите выбирать «community wiki» в своих ответах – CupawnTae

+0

В настоящее время я использую тот же самый код, и он работает нормально. –

+0

Правильно, но, как я сказал в вопросе, я уже мог решить проблему по-другому, я специально задавал вопрос о заказе обновлений стиля. (Кроме того, FYI, в общем случае, только для кода ответы без объяснения здесь не рассматриваются). Спасибо за ввод, хотя! – CupawnTae