Dunno123
Dunno123

Reputation: 69

How to detect when all keys are released

The following code is supposed to measure how long a key (or more keys) was pressed.

var output = document.getElementById('output'),
    pressed = {};

window.onkeydown = function(e) {
    if ( pressed[e] ) return;
    pressed[e] = e.timeStamp;
};

window.onkeyup = function(e) {
    if ( !pressed[e] ) return;   
    var duration = ( e.timeStamp - pressed[e] ) / 1000;
    var today = new Date();
    var date = today.getDate()+'-'+(today.getMonth()+1)+'-'+today.getFullYear();
    var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
    var dateTime = date+' '+time; 
    output.innerHTML += dateTime + ' - Key(s) pressed for ' + duration + ' seconds</p>';
    pressed[e] = 0;
}
<div id="output"></div>

Is there any way to change the window.onkeyup function so that it makes the output log only when ALL keys are released?

Example:

10:00:00 - I press A and hold it

10:00:05 - I press B and hold it

10:00:20 - I press C and hold it

10:00:22 - I release A

10:00:24 - I release B

10:00:30 - I release C

Current code output:

26-2-2021 10:00:22 - Key(s) pressed for 22 seconds

26-2-2021 10:00:24 - Key(s) pressed for 2 seconds

26-2-2021 10:00:30 - Key(s) pressed for 6 seconds

Expected output:

26-2-2021 10:00:30 - Key(s) pressed for 30 seconds

Thank you for your help.

Upvotes: 1

Views: 398

Answers (4)

3limin4t0r
3limin4t0r

Reputation: 21130

You could use a Set then execute some code (like the onRelease method below) when the last key is removed from the set. To know what the duration was you'll also have to keep track of when the first key was pressed.

The example below moves the logic and the callback function into the pressed object, but there are many ways you could set this up.

const output = document.getElementById("output");

function log(text) {
  const p = document.createElement("p");
  p.append(text);
  output.append(p);
}

const pressed = {
  keys: new Set(),
  timestamp: null,
  get isEmpty() { return !this.keys.size },
  add(key) {
    // save the current timestamp when adding the first key
    if (this.isEmpty) this.timestamp = Date.now();
    return this.keys.add(key);
  },
  delete(key) {
    const wasPresent = this.keys.delete(key);
    // trigger onRelease when deleting the last key
    if (wasPresent && this.isEmpty) this.onRelease();
    return wasPresent;
  },
  onRelease() {
    const now = new Date();
    const duration = now.getTime() - this.timestamp;
    log(`${now.toLocaleString()} - Key(s) pressed for ${duration}ms`);
  }
};

document.addEventListener("keydown", ({ code }) => pressed.add(code));
document.addEventListener("keyup", ({ code }) => pressed.delete(code));
<div id="output"></div>

Similar to other answers, "keydown" and "keyup" only trigger when they are done with the page active in the current tab. When keys are pressed/released when on another tab/window they will not be registered with JavaScript.

Upvotes: 0

trincot
trincot

Reputation: 350300

First of all, you should do pressed[e.code], not pressed[e], as this e object value will just stringify to [object KeyboardEvent], and so you assign to just one and the same property, on each keyboard event.

To only report when all keys are up, keep track of a count and of the time the first key was pressed:

var output = document.getElementById('output'),
    pressed = {},
    keyDownCount = 0,
    keyDownTime = 0;

window.onkeydown = function(e) {
    if ( pressed[e.code] ) return;
    pressed[e.code] = true;
    if (!keyDownCount) keyDownTime = e.timeStamp;
    keyDownCount++;
};
    
window.onkeyup = function(e) {
    if ( !pressed[e.code] ) return;
    pressed[e.code] = false;
    keyDownCount--;
    if (keyDownCount) return;
    var duration = ( e.timeStamp - keyDownTime ) / 1000;
    var today = new Date();
    var date = today.getDate()+'-'+(today.getMonth()+1)+'-'+today.getFullYear();
    var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
    var dateTime = (date+' '+time).replace(/\b\d\b/g, "0$&"); 
    output.innerHTML += dateTime + ' - Key(s) pressed for ' + duration + ' seconds</p>';
}
<div id="output"></div>

Note that this is not completely bullet proof. If someone focusses another window and changes which keys are pressed before returning, these changes will not fire the events.

Upvotes: 1

Aasmund Berge Endresen
Aasmund Berge Endresen

Reputation: 347

There is a few things you want to change to get this to work. First off when you're resetting your cached timestamps after all keys are released you should remove the entry instead of just setting the timestamp to 0 - this way you will start over with an empty object again the next time you press multiple keys.

You can use the delete keyword for this.

Also from what I understand you really just want to compare the first timestamp to the last to get total amount of time you press the keys. So I would propose your solution to be (I also refactored a little):

const output = document.getElementById('output');
let pressed = {};

window.onkeydown = function (e) {
  if (Object.values(pressed).length <= 1) {
    pressed.start = new Date();
  }
  pressed[e.code] = e.timeStamp;
};

window.onkeyup = function (e) {
  delete pressed[e.code];
  const { start, ...rest } = pressed;
  if (rest.length <= 1) {
    const currentTime = new Date();
    const duration = (currentTime - start) / 1000;
    const date = `${currentTime.getDate()}-${currentTime.getMonth() + 1}-${currentTime.getFullYear()}`;
    const time = `${currentTime.getHours()}:${currentTime.getMinutes() + 1}:${currentTime.getSeconds()}`;
    output.innerHTML += `${date} ${time} - Key(s) pressed for ${duration} seconds</p>`;
    delete pressed.start;
  }
};

Upvotes: 0

Mike B
Mike B

Reputation: 2776

You can improve your pressed map with another value i.e. is_down and check it at the end.

window.onkeydown = function(e) {
    if ( pressed[e.key] ) return;
    pressed[e.key] = {time:e.timeStamp, is_down:true}
};

And then when released you check if is anyone with is_down:true.

window.onkeyup = function(e) {
    if ( !pressed?.is_down ) return;   
    var duration = ( e.timeStamp - pressed[e.key].time ) / 1000;
    var is_all_released = Object.keys(pressed).filter(key => pressed[key].is_down).length > 0
    if (is_all_released) {
        //do your printouts
    }

Upvotes: 0

Related Questions