【问题标题】:Click EventListener only running once单击仅运行一次的 EventListener
【发布时间】:2021-11-02 00:56:39
【问题描述】:

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

我用 setTimeout 做了一个循环。单击按钮时,会生成随机 rgb 值并将其应用于主体背景颜色。我使用了一个布尔标志,当再次单击按钮时,它被分配了假值,这会在检查 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>

【问题讨论】:

  • 您从未将 flag 设置回 true。请注意,您还重置了 onclick 事件处理程序并每 100 毫秒添加一个新的超时。您应该考虑改用setTimeout(makeRgb, 100);

标签: javascript dom-events addeventlistener


【解决方案1】:

我没有创建我们自己的临时间隔,而是使用了内置的 setInterval 函数。 setIntervalsetTimeout 都返回一个数字,您可以将其传递给 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>

为什么它似乎只被调用一次

如果(!标志){ 返回; }

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

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

button.onclick = function stop() { 标志=假; document.body.style.backgroundColor = '白色'; }

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

为什么不使用var

您很可能不想使用var。你很有可能永远不想使用var。只需使用let 来声明您打算修改的变量,并使用const 来声明应该是常量的变量。如果你真的关心为什么谷歌像“javascript var vs let vs const”这样的东西。但是,如果您刚刚开始学习 javascript,最好在您理解之前避免使用 var

颜色生成不正确

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

Math.random() 函数返回一个浮点伪随机数,范围为 0 到小于 1(包括 0,但不包括 1)

所以0 &lt;= Math.random() &lt; 1。你永远不会得到1,因为上限是独占

假设您得到9.99999999... 并将其乘以255。你永远不会得到255,但比这更小。然后你把它放在地板上。所以你会得到的最大数字是254。要解决这个问题,我建议乘以 256

写评论以获得更多帮助

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

【讨论】:

    【解决方案2】:
    1. 让您的按钮处理程序返回 a closure 以保持您的标志状态。这样你就没有任何全局变量了。

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

    3. makeRGB返回一个值,而不是直接设置元素的颜色。

    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>

    【讨论】:

      【解决方案3】:

      使用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>

      【讨论】:

        【解决方案4】:

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

        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);
            }
        })
        

        【讨论】:

        • 我的意思是,当然。你已经把代码变小了。但它对学习 javascript 的人真的有帮助吗?例如,您理所当然地认为他们理解Array(3).fill(0)。对于这么小的数组,我什至不会自己这样做。
        • 我只是喜欢非常干净的代码。当您回到它时,更容易看到它在做什么。我使用 Array(3).fill(0).map() 的唯一原因是因为它消除了一些重复。我会理解不使用它。
        • 更小并不总是意味着更干净;)
        • 随机部分不应该是 256 而不是 255,因为 floor 会将它向下舍入吗?由于地板,255 将最大为 254。
        • 当然,我已经在我的回答中更新了它:)
        猜你喜欢
        • 2018-11-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-07-31
        • 2017-10-11
        • 1970-01-01
        相关资源
        最近更新 更多