matisa
matisa

Reputation: 529

endless while loop after mousemove

I am going crazy here. I want to show an element on mouse move, and hide it 10 sec after last move of the mouse.

I wrote this:

document.addEventListener("DOMContentLoaded", function(event) {
  var time = 0;
  document.addEventListener("mousemove", function(event) {
    console.log('$');
    document.getElementsByClassName("mybar")[0].style.visibility = 'visible';
    time = 0;
    while (time < 11) {
      setTimeout(function() {
        time++
      }, 1000);
      console.log(time, time == 10);
      if (time == 10) {
        document.getElementsByClassName("mybar")[0].style.visibility = 'hidden';
      }
    }
  });
});
<div class='mybar'>
  <h1> TESTING </h1>
</div>

Why does it end up in an endless loop? Why doesn't it exit on condition? why does the if never gets the 'true' parameter? Notice : don't run it this way... it will kill your tab.

Upvotes: 0

Views: 375

Answers (3)

llama
llama

Reputation: 2537

First, you don't need to wait for DOMContentLoaded to add an event listener to document, since if you did, you couldn't add DOMContentLoaded in the first place.

The infinite loop is because setTimeout doesn't pause the script. It schedules its callback for the time you provide, and irrespective of that time, the callbacks will not run until the current running code in the thread completes, which never happens because you don't increment the time variable.

So the loop never ends, and so the thread is never made available, so your callbacks never can run, so time can never be incremented.

Lastly, starting a setTimeout inside an event handler that shares a local variable and executes very rapidly on an event like mousemove is prone to give unexpected results. For example, in your code, every time the handler runs, it'll reset time to 0, which doesn't seem to be what you'd want.


A solution would be to ditch the loop, schedule the visibility for 10 seconds, and prevent the main part of the code in the handler from running in the meantime by using a boolean variable.

var timer = null;
document.addEventListener("mousemove", function(event) {
    var myBar = document.querySelector(".mybar");
    if (!myBar) {
      return; // there's no mybar element
    }


    if (timer == null) {
      myBar.style.visibility = 'visible';
    } else {
      clearTimeout(timer); // clear the currently running timer
    }

    // set to hidden in 10 seconds
    timer = setTimeout(function() {
      myBar.style.visibility = 'hidden';
      timer = null; // clear the timer
    }, 10000);
});

I also switched to querySelector instead of getElementsByClassName because it's shorter and cleaner. And I used a variable to make sure the element is found before setting the style.

Upvotes: 2

Tom O.
Tom O.

Reputation: 5941

Here's a way to do it with regular JavaScript. If your browser isnt ES6 compliant, you can replace the arrow functions with regular function expressions. The example hides the text after 2 seconds instead of 10, just so you can see it work without having to waste 8 extra seconds.

//hide by default
document.getElementById('myBar').style.display = 'none';

var timer = null;

var hideDivTimer = () => {
  timer = setTimeout(() => {
    document.getElementById('myBar').style.display = 'none';
  }, 2000);
};

document.addEventListener('mousemove', () => {
  clearTimeout(timer);
  document.getElementById('myBar').style.display = 'inline';
  hideDivTimer();
});
<body>
  <div id='myBar'>
    <h1> TESTING </h1>
  </div>
</body>

Upvotes: 0

gunwin
gunwin

Reputation: 4832

You need a flag out of the mousemove scope that tells your listener that you've already ran.

if(running) return; 
running = true;

In context:

document.addEventListener("DOMContentLoaded", function(event) {
  var time = 0;
  var running = false;
  document.addEventListener("mousemove", function(event) {

    console.log('$');

    if(running) return;
    running = true;

    document.getElementsByClassName("mybar")[0].style.visibility = 'visible';
    time = 0;
    while (time < 11) {
      setTimeout(function() {
        time++
      }, 1000);
      console.log(time, time == 10);
      if (time == 10) {
        document.getElementsByClassName("mybar")[0].style.visibility = 'hidden';
      }
    }
  });
});

Upvotes: 0

Related Questions