仅单击 EventListener 运行 一次

Click EventListener only running once

我仍在学习 JavaScript 的基础知识,目前正在学习 eventListeners。我正在尝试制作一个按钮,单击它会将整个 body 的背景颜色更改为一些随机生成的 rgb 代码,每 100 毫秒,再次单击它时,背景颜色变回白色并停止颜色变化.

我用 setTimeout 做了一个循环。单击按钮时,会生成随机 rgb 值并将其应用于 body 背景色。我使用了一个布尔标志,当再次单击按钮时该标志被分配为假值,这会停止检查 if 条件的循环。我面临的问题是事件侦听器不工作超过一次点击。

代码如下:

const button = document.querySelector('#btn');

var flag = true;

button.addEventListener('click', function() {
  loop();
})

function makeRGB() {
  const r = Math.floor(Math.random() * 255);
  const g = Math.floor(Math.random() * 255);
  const b = Math.floor(Math.random() * 255);
  const colorID = `rgb(${r},${g},${b})`;
  document.body.style.backgroundColor = colorID;
}

function loop() {
  if (!flag) {
    return;
  }
  makeRGB();
  setTimeout(loop, 100);
  button.onclick = function stop() {
    flag = false;
    document.body.style.backgroundColor = 'white';
  }
}
h1 {
  text-align: center;
}

button {
  margin: auto;
  display: block;
}
<h1 id="heading">Welcome!</h1>
<button id="btn">Change Color! </button>

我没有创建自己的临时间隔,而是使用了内置的 setInterval 函数。 setIntervalsetTimeout return 一个数字,您可以将其传递给 clearIntervalclearTimeout 以停止异步代码执行。

const button = document.querySelector('#btn');

let changeColorInterval;

button.addEventListener('click', function() {
    // if we currently have an interval running, e.g. != undefined
    if (changeColorInterval) {
        // remove the interval, e.g. stop the execution of makeRGB
        clearInterval(changeColorInterval);
        // set the variable back to undefined, otherwise next time
        // you click the button this branch of the if statement
        // will be executed, even though the interval is not
        // actually running anymore.
        changeColorInterval = undefined;
        // restore the background to white like you wanted.
        document.body.style.backgroundColor = "white";
        
      // If we don't have an interval, create one
    } else changeColorInterval = setInterval(makeRGB, 100);

})

function makeRGB() {
    const r = Math.floor(Math.random() * 256);
    const g = Math.floor(Math.random() * 256);
    const b = Math.floor(Math.random() * 256);
    const colorID = `rgb(${r},${g},${b})`;
    document.body.style.backgroundColor = colorID;
}
h1 {
    text-align: center;
}

button {
    margin: auto;
    display: block;
}
<h1 id="heading">Welcome!</h1>
  <button id="btn">Change Color! </button>

为什么好像只调用了一次

if (!flag) { return; }

每次按下按钮时都会调用事件侦听器本身。您可以看到,如果您在点击回调中放置一个 console.log("click"),就在 loop() 之前。问题是您从未将变量分配回 true,因此它始终存在该函数。

不要在事件侦听器中添加事件侦听器...除非您真的打算这样做

button.onclick = function stop() { flag = false; document.body.style.backgroundColor = 'white'; }

您在事件侦听器中分配了一个“老式”事件侦听器,这似乎不是一个好主意。参见:addEventListener vs onclick

为什么不用var

您很可能不想使用 var。您很可能不想使用 var。只需使用 let 来声明您打算修改的变量,并使用 const 来声明应该为常量的变量。如果你真的关心为什么 google 像“javascript var vs let vs const”。但是,如果您刚刚开始学习 javascript,最好避免使用 var,直到您理解它。

颜色生成不正确

你的颜色生成有点错误。 As described by mozilla

The Math.random() function returns a floating-point, pseudo-random number in the range 0 to less than 1 (inclusive of 0, but not 1)

所以0 <= Math.random() < 1。你永远不会得到 1 因为上限是 exclusive.

假设您得到 9.99999999... 并将其乘以 255。你永远不会得到 255 但比这更小的东西。然后你把它铺平。因此,您将获得的最大数量是 254。要解决这个问题,我建议乘以 256.

发表评论以获得更多帮助

如果您在理解代码方面需要任何其他帮助,请在此答案的评论中回复我:)

使用setInterval而不是setTimeout来连续改变背景颜色,直到再次点击按钮

const button = document.querySelector('#btn');

var flag = false;

var startChange;

button.addEventListener('click', function() {
  flag = !flag;
  loop();
})

function makeRGB() {
  const r = Math.floor(Math.random() * 255);
  const g = Math.floor(Math.random() * 255);
  const b = Math.floor(Math.random() * 255);
  const colorID = `rgb(${r},${g},${b})`;
  document.body.style.backgroundColor = colorID;
}

function loop() {
  if (!flag) {
    clearInterval(startChange);
    document.body.style.backgroundColor = 'white';
    return;
  }
  startChange = setInterval(makeRGB, 100);
}
h1 {
  text-align: center;
}

button {
  margin: auto;
  display: block;
}
<h1 id="heading">Welcome!</h1>
<button id="btn">Change Color! </button>

这里是您的代码的重新格式化。可能会被清理得更多,但应该做你想做的事。这里我们没有覆盖间隔,而是将其删除。

const randomColor = () => Array(3).fill(0).map(() => Math.floor(Math.random() * 256)).join();

let loop;

document.querySelector("#btn").addEventListener("click", (e) => {
    if(loop){
        clearInterval(loop);
        loop = undefined;
        document.body.style.backgroundColor = "white";
    } else {
        loop = setInterval(() => {
            document.body.style.backgroundColor = `rgb(${randomColor()})`;
        }, 100);
    }
})
  1. 让您的按钮处理程序 return a closure 保持您的标记状态。这样你就没有任何全局变量了。

  2. 使用setTimeout,并且仅在满足条件时调用它。这样你就不必 clear 任何东西。

  3. makeRGBreturn一个值而不是直接设置元素的颜色。

const button = document.querySelector('#btn');

// When you call `handler` it returns a new function 
// that is called when the button is clicked
button.addEventListener('click', handler(), false);

function makeRGB() {
  const r = Math.floor(Math.random() * 255);
  const g = Math.floor(Math.random() * 255);
  const b = Math.floor(Math.random() * 255);
  return `rgb(${r},${g},${b})`;
}

// Initially set `flag` to false
function handler(flag = false) {

  // Cache the document body element
  const body = document.body;

  // Return the function that serves as
  // the click listener
  return function() {

    // Reset the flag when the button is clicked
    flag = !flag

    // Start the loop
    function loop() {

      // If `flag` is true set the new colour
      // and call `loop` again
      if (flag) {
        body.style.backgroundColor = makeRGB();
        setTimeout(loop, 100);

      // Otherwise set the background to white
      } else {
        body.style.backgroundColor = 'white';
      }
    }

    loop();

  }
}
h1 { text-align: center; }
button { margin: auto; display: block; }
<h1 id="heading">Welcome!</h1>
<button id="btn">Change Color! </button>