Избегайте объединения изменений DOM вместе

#javascript #dom

Вопрос:

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

Мой код выглядит примерно так:

 function onclick() {
    showLoader();
    
    longDOMWriteCall();
    
    hideLoader();
}

function showLoader() {
    loader.classList.remove("hidden");
}

function hideLoader() {
    loader.classList.add("hidden");
}
 

Создается впечатление, что по какой-то причине браузер show hide одновременно запускает вызовы и, а также вызов функции записи, в результате чего загрузчик никогда не появляется в первую очередь, несмотря на синхронность кода. Даже выполнение операции асинхронным с использованием обещаний дает тот же результат:

 function onclick() {
    showLoader();
    
    longDOMWriteCall().then(hideLoader);
}
 

Как я могу предотвратить это, чтобы загрузчик появился, функция сделала свое дело, а затем загрузчик снова исчез? Этот эффект незаметен на устройстве высокого класса, но на устройстве низкого класса, с которым я тестировал, может потребоваться до половины секунды для завершения вызова.

Скрипка: https://jsfiddle.net/Emosewaj/u07o8ybj/33/

Ответ №1:

Запустите код асинхронно с setTimeout() помощью .

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

 const loader = document.getElementById("loader");
const container = document.getElementById("container");

function clickFuncAsync() {
  showLoader();

  longDOMWriteCallAsync().then(hideLoader);
}

function longDOMWriteCallAsync() {
  console.log("Running async");
  return new Promise(resolve => {
    setTimeout(() => {
      let html = "";

      for (let i = 0; i < 5; i  ) {
        html  = "<div>Content</div>";
      }

      container.innerHTML = html;
      resolve();
    }, 1000);
  });
}

function showLoader() {
  loader.classList.remove("hidden");
}

function hideLoader() {
  loader.classList.add("hidden");
} 
 #loader {
  height: 32px;
  width: 32px;
}

#loader.hidden {
  display: none;
} 
 <button onclick="clickFuncAsync()">
    Run Async
</button>

<div id="loader" class="hidden">
  <img src="https://picsum.photos/32/32">
</div>

<div id="container">
</div> 

Комментарии:

1. Спасибо! Именно то, что мне было нужно!

Ответ №2:

 const loader = document.getElementById("loader");
const container = document.getElementById("container");

function clickFunc() {
  showLoader();
  setTimeout(()=> {
    longDOMWriteCall();
    hideLoader();
  }, 1)
}

function clickFuncAsync() {
  showLoader();
  setTimeout(()=> {
    longDOMWriteCallAsync()
    hideLoader()
  }, 1)
}

function longDOMWriteCall() {
    console.log("Running sync");
    let html = "";
    for (let i = 0; i < 50000; i  ) {
        html  = "<div>Content</div>";
    }
    container.innerHTML = html;
}

function longDOMWriteCallAsync() {
    console.log("Running async");
    return new Promise(resolve => {
        let html = "";
        for (let i = 0; i < 50000; i  ) {
            html  = "<div>Content</div>";
        }
        container.innerHTML = html;
        resolve();
    });
}

function showLoader() {
  loader.classList.remove("hidden");
  console.log('sdfds')
}

function hideLoader() {
console.log('vvvvv')
    loader.classList.add("hidden");
} 
 #loader {
    height: 32px;
    width: 32px;
}

#loader.hidden {
    display: none;
} 
 <button onclick="clickFunc()">
    Run
</button>
<button onclick="clickFuncAsync()">
    Run Async
</button>
<div id="loader" class="hidden">
    <img src="https://picsum.photos/32/32">
</div>


<div id="container">
</div> 

Вы можете попробовать тайм-аут остроумия:

 showLoader();
  setTimeout(()=> {
    longDOMWriteCallAsync()
    hideLoader()
  },1)