如何在函数执行期间show/hide动画GIF?

How to show/hide animated GIF during the execution of a function?

我想在函数执行期间显示动画 GIF(微调器图标),然后在函数完成时隐藏 GIF。

但是,我正在实现的代码显然没有显示 GIF,因为该函数正在占用线程并且不允许显示 GIF。

示例:(https://jsfiddle.net/jzxu8cLt/)

HTML
<button onclick="go();">Go</button>
<span id="result"></span>
<hr>
<img style="display: none;" id="spinner" src="https://upload.wikimedia.org/wikipedia/commons/b/b1/Loading_icon.gif">
JS
function longLoop() {
  let count = 1000000000;
  let result = 0;
  for (let i = 1; i <= count; i++) {
    result = i;
  }
  return result;
}

function go() {
  let spinnerElement = document.getElementById('spinner');
  let resultElement = document.getElementById('result');
  spinnerElement.style.display = 'block';
  let result = longLoop();
  resultElement.innerHTML = result;
  spinnerElement.style.display = 'none';
}

上面的 go() 函数应该在调用 long 运行 函数之前完成显示 GIF 的工作,然后在 long 运行 函数完成时隐藏 GIF。

但是,GIF 从来没有机会在浏览器中显示,我不知道如何让 GIF 显示。注意:如果我在调用 longLoop() 函数的行放置断点,GIF 将正确显示。

我需要做什么才能让 GIF 在 longLoop() 函数期间显示和动画化?

运行 你的 long 运行 并在 setTimeout 中恢复代码,省略计时参数或 0 将“立即”执行,或更准确地说,下一个事件循环,这将防止阻塞。

function longLoop() {
  let count = 1000000000;
  let result = 0;
  for (let i = 1; i <= count; i++) {
    result = i;
  }
  return result;
}

function go() {
  let spinnerElement = document.getElementById('spinner');
  let resultElement = document.getElementById('result');
  spinnerElement.style.display = 'block';
  setTimeout(() => {
    let result = longLoop();
    resultElement.innerHTML = result;
    spinnerElement.style.display = 'none';
  })
}
<button onclick="go();">Go</button>
<span id="result"></span>
<hr>
<img style="display: none;" id="spinner" src="https://upload.wikimedia.org/wikipedia/commons/b/b1/Loading_icon.gif">

我建议您使用 async/await 在这里 pourpuse 的代码将更具可读性解决方案:

function longLoop(timeout) {
  return new Promise(resolve => setTimeout(resolve, timeout));
}

async function go() {
  let spinnerElement = document.getElementById('spinner');
  let resultElement = document.getElementById('result');
  spinnerElement.style.display = 'block';
  let result = await longLoop(4000);
  
  spinnerElement.style.display = 'none';
 
}

是的,你是对的,因为 JavaScript 是 single-threaded,longLoop 函数阻塞了事件循环,不给浏览器渲染 gif 的机会。

JsFiddle 解决方案:https://jsfiddle.net/12yf4tgd/

我使用 setTimeout 使 longLoop 函数异步并将其包装在 Promise 中,以便其他作业 运行 完成后完成。

function go() {
  let spinnerElement = document.getElementById('spinner');
  let resultElement = document.getElementById('result');
  spinnerElement.style.display = 'block';

  new Promise(resolve => setTimeout(() => {
    resolve(longLoop())
  }))
  .then((result) => {
    spinnerElement.style.display = 'none';
    resultElement.innerHTML = result;   
  })
}

由于它由某些 Web Api 在别处处理,而不是在主调用堆栈上,因此不会阻塞。

如果你不想使用 .then() 因为所有嵌套,另一个选择是使 go() 和异步函数利用 async/await 语法。

async function go() {
  let spinnerElement = document.getElementById('spinner');
  let resultElement = document.getElementById('result');
  spinnerElement.style.display = 'block';
  let result = await new Promise((resolve, reject) => {
    setTimeout(() => resolve(longLoop()))
  })
  spinnerElement.style.display = 'none';
  resultElement.innerHTML = result;
}

它们的工作方式相同。