Basj
Basj

Reputation: 46463

In-browser screensaver with Javascript

Context: For a kiosk application, and for out-of-topic reasons (see here), I don't rely on the OS screensaver, but rather implement a screensaver in JS : a black modal layer (full width, full height) with a logo appears after 5 minutes of user inactivity.

Is it sub-optimal (performance-wise) to clearTimeout and setTimeout in such a way, i.e. for each mousemove (this can happends hundreds of times in a few seconds): is there a better way to handle this screensaver triggering?

Here is what I use, it works (here the delay is 2 seconds):

var screensaver_task = null;
var start_screensaver = () => {
    document.querySelector(".screensaver-layout").classList.add("hidden");
    if (screensaver_task) {
        clearTimeout(screensaver_task);
        screensaver_task = null;
    }
    screensaver_task = setTimeout(() => { 
        document.querySelector(".screensaver-layout").classList.remove("hidden");
    }, 2 * 1000);
}
document.addEventListener("mousemove", start_screensaver);
document.addEventListener("click", start_screensaver);
start_screensaver();
.screensaver-layout { background: black; z-index: 1000; position: fixed; top: 0; left: 0; width: 100%; height: 100%; }
.hidden { display: none; }
Mouse move here and wait 2 seconds...
<div class="screensaver-layout hidden"></div>

Upvotes: 0

Views: 881

Answers (3)

Reyno
Reyno

Reputation: 6505

Here is a simple solution with a debounce function that takes two callbacks, one to run at the start and one that runs at the end.

const debounce = (start, end, ms = 500) => {
  let timerId;
  let started = false;

  return (...args) => {
    clearTimeout(timerId);

    // Execute on startup
    if (!started) {
      start(...args);
      started = true;
    }

    // Execute when debounced finished
    timerId = setTimeout(() => {
      end(...args);
      started = false;
    }, ms);
  };
};

const debounceStarted = () => {
  document.querySelector(".screensaver-layout").classList.add("hidden");
}

const debounceEnded = () => {
  document.querySelector(".screensaver-layout").classList.remove("hidden");
}

document.addEventListener("mousemove", debounce(debounceStarted, debounceEnded, 2000));
.screensaver-layout {
  background: black;
  z-index: 1000;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

.hidden {
  display: none;
}
<div class="screensaver-layout hidden"></div>

Upvotes: 1

gog
gog

Reputation: 11347

Just record the last time you saw any interactive event, and have a screensaver "thread" that checks the last event time and acts accordingly.

<style>
  #screensaver {
    display: none
  }
  
  .has-screensaver #screensaver {
    display: block
  }
</style>

<div id="screensaver">screensaver</div>

<script>
  const INTERACTIVE_EVENTS = [
    'mousedown',
    'mousemove',
    'touchstart',
    'scroll',
    'wheel',
    'keydown',
    // @TODO anything else?
  ];

  const SCREENSAVER_TIMEOUT = 3000


  window.onload = () => {

    let lastEventTime = new Date

    function screenSaver() {
      let elapsed = new Date - lastEventTime
      document.body.classList.toggle(
        'has-screensaver',
        elapsed > SCREENSAVER_TIMEOUT)
      setTimeout(screenSaver, 100)
    }

    INTERACTIVE_EVENTS.forEach(e =>
      window.addEventListener(e,
        () => lastEventTime = new Date()))

    screenSaver()

  }
</script>

Upvotes: 0

AKX
AKX

Reputation: 168967

I'd do as little work as possible in a mousemove handler.

Since I expect you don't need the screen saver to start exactly 2 seconds (or whatever) after the last move, I'd just have a slow interval running all the time that checks whether it's been enough time since the last mouse move, like so:

let lastMouseTime = 0;
let screensaverRunning = false;

function updateRunning(flag) {
  screensaverRunning = flag;
  document
    .querySelector(".screensaver-layout")
    .classList.toggle("hidden", !screensaverRunning);
}

setInterval(() => {
  if (!lastMouseTime) return;
  if (!screensaverRunning) {
    if (+new Date() - lastMouseTime >= 2000) {
      updateRunning(true);
    }
  }
}, 1000);
document.addEventListener("mousemove", () => {
  lastMouseTime = +new Date();
  if (screensaverRunning) {
    updateRunning(false);
  }
});
.screensaver-layout {
  background: rgba(0, 255, 0, 0.5);
  z-index: 1000;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

.hidden {
  display: none;
}
Mouse move here and wait approximately 2 seconds...
<div class="screensaver-layout hidden"></div>

Upvotes: 4

Related Questions