Перекомпоновка/макет не запускается при обратном вызове?

На основе этого вопроса: Как узнать направление прокрутки IntersectionObserver?

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

Я читал суть Пола Айриша what-forces-layout.md, и мой вопрос очень просто.

Наличие ввода без обратного вызова в элементе body определенно запускает макет, см. пример ниже:

element.focus() запускает макет

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <input type="text">
  <script type="text/javascript">
    var elementB = document.querySelector('input');

    elementB.focus();
  </script>
</body>
</html>

see chrome performance record

Но если обернуть событие focus обратным вызовом щелчка, макет/перекомпоновка не срабатывает.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <input type="text">
  <script type="text/javascript">
    var elementB = document.querySelector('input');

    function onClick() {
      elementB.focus();
    }

    document.addEventListener('click', onClick);
  </script>
</body>
</html>

see chrome performance record

Итак, мой вопрос, почему не запускается макет/перекомпоновка?


person Jose Paredes    schedule 12.04.2018    source источник
comment
На такой вопрос только те, кто создал инструменты разработки, могут ответить твердым утверждением, например, может случиться так, что инструменты разработки не регистрируют короткозамкнутые перекомпоновки, потому что макет не изменился, и, следовательно, перекомпоновка нечего было делать.   -  person Kaiido    schedule 25.04.2018
comment
@Kaiido, это имеет смысл, также очень сложно воспроизвести это в разных браузерах.   -  person Jose Paredes    schedule 25.04.2018


Ответы (2)


Я не уверен, правильно ли я понял ваш вопрос, но в случае 1, когда синтаксический анализатор начинает выполнение сценария, DOMContentLoaded еще не запускается и все еще анализирует остальную часть документа. Пока вы вызываете focus на elemB, вы немедленно запускаете поток макета.

В случае 2 функция onClick вообще не вызывается, пока вы не щелкнете по самому документу. Вы можете убедиться в этом, включив "Paint Flashing" на предоставленной вами скрипке. Ввод станет зеленым только при нажатии.

Тогда как в первом случае вы видите краткую вспышку ввода при запуске (это ваш вызов .focus), а затем весь documentElement (в DOMContentLoaded).

Во втором случае у вас только один раз мигает весь documentElement (на DOMContentLoaded, при условии, что ничто другое не вызывает событие перекомпоновки/перерисовки при загрузке), а затем только элемент ввода один раз за щелчок.

PS:

Теперь, насколько я вижу, я попробовал ваши 2 случая на моей локальной машине, и, что интересно, в вашем первом случае я вижу действия 2 layout сразу после DOMContentLoaded.

Однако если я закомментирую строку elementB.focus(); из вашего случая 1 и запишу снова, я снова увижу 2 действия макета.

Насколько я понимаю, браузер будет выполнять 2 операции макета при запуске, как только он начнет анализировать тело, а затем один раз вокруг DOMContentLoaded. И если какой-либо синхронный принудительный сброс макета выполняется с помощью javascript (путем вызова любого из методов/свойств, перечисленных в вашей ссылке), браузер попытается пакетировать эти операции.

Чтобы проверить это поведение, я изменил ваш 1-й случай, как показано ниже:

!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <input type="text" style="position:relative;top:0px;">
  <script type="text/javascript">
    var elementB = document.querySelector('input');
    elementB.focus();
  </script>
  <script async="true">
    setTimeout(function(){
        elementB.style.top = parseInt(elementB.style.top) + 5 + "px";
    },500)
  </script>
</body>
</html>

Теперь произойдет то, что у вас будет третье действие макета сразу после (~ 500 мс) события загрузки (асинхронность не нужна). Но если бы вы сделали setTİmeout 0 мс, вы снова получили бы 2 действия макета! (поведение очереди микрозадач может быть не гарантировано, если вы видите 3 макета, чтобы принудительно синхронизировать макет, удалите атрибут async и обертку setTimeout внутри второго тега script). Итог: Итак, браузер выполняет пакетную обработку, или, по крайней мере, это то, что я вижу в этом примере.

Что касается вашего второго случая, когда я записываю его так, как вы разместили, правильно, что я не вижу активности макета (2 макета, как и раньше). Но то, что я вижу, — это последовательный пересчет стиля + обновление дерева макета + отрисовка после каждого события. Это заставляет меня думать, что после обновления дерева макета, если удаление макета не требуется, оно не пересчитывается. Чтобы проверить это поведение, я изменил ваш второй скрипт, как показано ниже:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <input type="text" style="position:relative;top:0px;">
  <script type="text/javascript">
    var elementB = document.querySelector('input');

    function onClick() {
      elementB.focus();
      elementB.style.top = parseInt(elementB.style.top) + 5 + "px";
    }

    document.addEventListener('click', onClick);
  </script>
</body>
</html>

Здесь каждый раз, когда вы щелкаете документ, поле ввода перемещается вниз на 5 пикселей. Если вы запишете в течение 10 секунд несколько кликов, вы увидите много обновления дерева макета + перерисовки И уничтожения макета. Это заставляет меня думать, что удаление макета выполняется после обновления дерева макета, если это необходимо.

ВЫВОД (могу ошибаться)

  • Браузер попытается выполнить пакетную обработку макета во время синтаксического анализа HTML.
  • element.focus вызовет перерисовку + обновление дерева макета, но уничтожение макета не гарантируется (по крайней мере, из этих примеров)
person ibrahim tanyalcin    schedule 23.04.2018
comment
Я не это имел в виду, говоря о них. Когда вы не вызываете функцию, перекомпоновка не будет запущена, вот и все, что нужно. Если вы сделаете где-нибудь elem.scrollTop, это вызовет перекомпоновку, но function(){elem.scrollTop ..} не будет, пока она не будет вызвана. Вот почему их обертывание не вызывает перекомпоновку. - person ibrahim tanyalcin; 23.04.2018
comment
проблема не в переносе, проблема в том, что когда я нажимаю на документ, reflow должен срабатывать во втором скрипте, но он не срабатывает, поэтому мой вопрос, почему так? в обоих случаях вызывается elementB.focus() - person Jose Paredes; 23.04.2018
comment
Если я включу мигание краски и нажму на документ, я ясно увижу перекомпоновку - person ibrahim tanyalcin; 23.04.2018
comment
repaint не то же самое, что reflow то, что вы видите на мигающей краске, это repaint - person Jose Paredes; 23.04.2018
comment
Да правильно, я обновлю с PS, я думаю, что у вас есть ложное срабатывание. - person ibrahim tanyalcin; 23.04.2018
comment
But if you were to make the setTİmeout 0ms, you would get 2 layout activities again! Я попробовал это, и я все еще вижу 3 действия с макетами - person Jose Paredes; 25.04.2018
comment
Поведение очереди микрозадач может быть не гарантировано. В этом случае, чтобы принудительно синхронизировать макет, удалите обертку setTimeout вокруг второго скрипта и включите асинхронный атрибут. Вы снова получите 2 макета. - person ibrahim tanyalcin; 25.04.2018
comment
Атрибут async/defer не влияет на встроенные скрипты, так что это не имеет большого значения. - person Jose Paredes; 25.04.2018
comment
да, я тоже сказал это в своем ответе. Однако основная идея моего ответа на ваш вопрос воспроизводима на основе этих примеров. - person ibrahim tanyalcin; 25.04.2018

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

Но перекомпоновка происходит, по крайней мере, если это необходимо.

Чтобы протестировать перекомпоновку, лучший способ, IMO, — запустить что-то, что в этом нуждается, и переходы CSS — это такая вещь.

var input = document.querySelector('input');
// test our logic without anything that should trigger a reflow 
noreflow.onclick = function() {
  // from 20px in CSS
  test.style.transition = 'none';
  test.style.width = '100px'; // should be ignored
  test.style.transition = 'width 1s linear';
  test.style.width = '20px';
  // should go from 20px to 20px => no transition
};
// test with manually triggered reflow
reflow.onclick = function() {
  // from 20px in CSS
  test.style.transition = 'none';
  test.style.width = '100px';
  input.focus(); // reflow
  // now should be computed as 100px
  test.style.transition = 'width 1s linear';
  test.style.width = '20px';
  // now that should move
}
#test {
  width: 20px;
  height: 20px;
  background: red;
}
<input type="text"><br>
<button id="noreflow">no reflow</button>
<button id="reflow">reflow</button>
<div id="test"></div>

person Kaiido    schedule 25.04.2018