Javascript forgets about other keydown when a keyup is triggered - javascript

I followed this question/answer to create a multiple key press detector. It works as intended: you press D and the square moves right, then you press S while pressing the D and the square moves in angle. So far so good. However, if you are pressing D and S at the same time and you unpress the S, the square will stop moving. The expected behaviour is that it keeps moving to the right, since you are still pressing D.
Here's a jsfiddle and the demo I'm making (my web).
So, why is .keyup() making other .keydown() event invalid? This is the offending jQuery code:
// Check if it's a function
function isFunction(functionToCheck) {
var getType = {};
return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
}
// Store the keys pressed and callback for each key
var keys = {};
var keycalls = {};
// Loop and call all the needed callbacks
function run_keys() {
$.each(keys, function (index, value) {
if (value == true) {
console.log(keys);
var fun = keycalls[index];
if (isFunction(fun))
fun();
}
});
}
// Store a key
$(document).keydown(function (e) {
keys[e.which] = true;
run_keys();
});
// Delete a key
$(document).keyup(function (e) {
delete keys[e.which];
run_keys();
});
// Assign a callback to a key
keyboard = function (key, callback) {
console.log(callback);
keycalls[key.toUpperCase().charCodeAt()] = callback;
}
// Assign keys
// Assign w to up action
keyboard("w", function () {
$(".keyboard").animate({
'top': "-=5px"
}, 0);
});
keyboard("a", function () {
$(".keyboard").animate({
'left': "-=5px"
}, 0);
});
keyboard("s", function () {
$(".keyboard").animate({
'top': "+=5px"
}, 0);
});
keyboard("d", function () {
$(".keyboard").animate({
'left': "+=5px"
}, 0);
});
Disclaimer: this is just for fun and learning, not about having a game engine. I know there are many good ones already available.
UPDATE
One of the solutions I've thought is making an independent loop for calling run_keys(), but I'd like to avoid that if possible. It works, however I'm still interested in the problem.

I believe your problem is coming from the end of keypress events after the keyup, rather than anything "going wrong", use a setTimout loop instead of calling run_keys at the end of each handler.
Here is an example of a setTimeout loop
(function looper() {
run_keys();
window.setTimeout(looper, 100);
}());
It's a bit like setInterval but the delay starts after the invocation, meaning you won't get (the same kind of) cascading errors as is possible with setInterval.

Related

JS how to have keyDown for a limited amount of time [duplicate]

I need to detect when a user keep press a button for X second and call function in JavaScript. Could you point me out in right direction?
You can use a setTimeout function on the keydown event which will fire after X seconds to compare the time that event was triggered to the last time a keyup was triggered - if at all.
var lastKeyUpAt = 0;
$(elem).on('keydown', function() {
// Set key down time to the current time
var keyDownAt = new Date();
// Use a timeout with 1000ms (this would be your X variable)
setTimeout(function() {
// Compare key down time with key up time
if (+keyDownAt > +lastKeyUpAt)
// Key has been held down for x seconds
else
// Key has not been held down for x seconds
}, 1000);
});
$(elem).on('keyup', function() {
// Set lastKeyUpAt to hold the time the last key up event was fired
lastKeyUpAt = new Date();
});
elem here is the element you're wanting to handle the event on.
JSFiddle demo.
Check out the jquery docs, they're super awesome. Something like this could give you the gist, though.
var keyTO,
seconds = 5;
$( "#i" ).on({
keypress: function(e) {
if(keyTO) {
clearTimeout(keyTO);
keyTO = null;
}
keyTO = setTimeout(someFn, seconds*1000);
},
keyup: function(e) {
clearTimeout(keyTO);
keyTO = null;
}
});
function someFn() {
// only called after full timeout
alert('Sweet.');
}
Watch for keydown and keyup events. jQuery's keydown event triggers recursively based on the user's keyboard settings for press and hold of keys. You can alternatively set your own recursive checking timeout if you would like to check this more/less frequently or abstract your checking away from varying user's keyboard settings.
This code (thanks to nbrooks for the prompting), monitors each key down event as their own instance, and so will allow for multiple keys to be press and held on a keyboard, each with their own triggertimes.
$(function(){
var keyDownArray = {};
var triggerTime = 3000; // number of ms to trigger function at
var keyDownEvent = function(e){
// use the event keyCode as a unique identifier
if(!(e.keyCode in keyDownArray) ){
// use the event keyCode as a unique identifier
// use setTimeout to make sure trigger only occurs once
keyDownArray[e.keyCode] = setTimeout(function(){
// call your function here (can pass in e.keyCode if function requires custom functions on a per key basis)
console.log('triggered for key '+e.keyCode);
}, triggerTime);
}
}
var keyUpEvent = function(e){
// on key release, clearTimeout and delete object key
clearTimeout(keyDownArray[e.keyCode]);
delete keyDownArray[e.keyCode];
};
$(document).on('keydown', keyDownEvent)
.on('keyup', keyUpEvent);
});
JSFiddle Demo
PS. it may be worth adding a throttler (something like Ben Almans debounce/throttle plugin) to decrease the potential load of this function
jsFiddle
You can accomplish this simply by recording the time at which a key is pressed, and then checking the time again when the key is released. Construct a JS object which maps key-codes to dates.
On keyDown, record the key and current time (if it's not already set—some keys trigger multiple keydowns while being held). On keyUp, find the time difference, clear the map entry, and then process accordingly:
$(function () {
var keyTimes = {};
$(document).keydown(function (e) {
if (!keyTimes["key" + e.which]) {
keyTimes["key" + e.which] = new Date().getTime();
}
});
$(document).keyup(function (e) {
if (keyTimes["key" + e.which]) {
var x = new Date().getTime() - keyTimes["key" + e.which];
delete keyTimes["key" + e.which];
// key was held for x/1000.0 seconds
}
});
});
Use keydown() and keyup() to check for the keypress.
Get the current system time with keydown() and compare that with the current system time when you get the keyup().
var starttime;
var curKey;
$('#seconds-counter').keydown(function(e) {
if (curKey != e.which) {
var d = new Date();
starttime = d.getTime();
$('#down-time').text(starttime);
curKey = e.which;
}
});
$('#seconds-counter').keyup(function() {
var d = new Date();
var endTime = d.getTime();
$('#up-time').text(endTime);
var timeTaken = endTime - starttime;
$('#result-val').text(timeTaken / 1000);
curKey = null;
});
JS Fiddle
(Edit: forgot the last bit :-) ) Call your function at the end of the keyup(), after curKey = null.

Throttle a button click with a function that requires a parameter that is a button attribute - Javascript

struggling to wrap my head around this one. I've just delved into the world of throttling, closures and avoiding global variables.
I have multiple buttons:
<a content_name="home" class="menuLink"><span>HOME</span></a>
I would like to throttle clicks on each of these buttons until getContent is done (for the sake of the question let's assume it will take 200ms).
My javascript:
function getContent(contentName) {
//Does some things
}
// Simply throttle function
function throttle(callback, limit) {
var throttleLock = false;
console.log('throttling: ' + throttleLock);
return function () {
if (!throttleLock) {
callback.call();
throttleLock = true;
setTimeout(function () {
throttleLock = false;
}, limit);
}
}
};
// Event listeners for the buttons
var menuButtons = document.getElementsByClassName("menuLink");
var menuBtnClick = function() {
var attribute = this.getAttribute('content_name');
getContent(attribute);
};
for (var i = 0; i < menuButtons.length; i++) {
menuButtons[i].addEventListener('click', throttle(menuBtnClick, 500), false);
}
If I refactor as this (without the throttle function) everything works fine, except that it doesn't throttle (obviously):
for (var i = 0; i < menuButtons.length; i++) {
menuButtons[i].addEventListener('click', menuBtnClick, 200), false);
}
The log error is:
Uncaught TypeError: this.getAttribute is not a function
at menuBtnClick ([this is my filename and line])
at HTMLAnchorElement.<anonymous> ([this is my filename and line])
I know that the throttle function is firing ( console log ), but it doesn't seem to fire the callback and the value of throttleLock remains false.
Please help, I'm sure I've done something wrong and I want to learn. If there are any alternatives to this attempt please fire away I'm all ears.
Thanks.
Just apply context to callback call:
callback.call(this);
Here is the example:
https://jsbin.com/yekimaqelo/1/edit?html,js,console,output

Modifying number of arguments for callback functions - javascript

I know many of you already used JavaScript UI widget plugins, etc... that offers callback functions. For instance we have Object x and it has a function, let say .doThisAfterAnEvent(). And according to the official documentation of Object x, that function accepts a single parameter of type function() with one argument, let say _args.
To visualize, here is the example:
var handler = function(_args) {
// Do something.
}
var x = $("#element-to-widget-ify").transform()
x.doThisAfterAnEvent(handler)
My question is, how can I modify the method .doThisAfterAnEvent() to accept a function with two or more parameters instead of one? In this case, I need to pass a second extra value to the handler function.
Edit:
var widgets = {
"widget-at-the-nav": $("#nav-widget").transform(),
"widget-at-the-footer": $("#nav-footer").transform(),
"widget-at-the-search": $("#nav-search").transform(),
length: 3
}
var handler = function(_args, _option) {
console.log("key in: " + _option
// Other processes...
}
for(key in widgets) {
console.log("key outer: " + key)
widget[key].doThisAfterAnEvent(function(json) {
console.log("key out: " + key)
handler(json, key)
})
}
This is my attempt. But it prints like this:
key outer: widget-at-the-nav
key outer: widget-at-the-footer
key outer: widget-at-the-search
key out: widget-at-the-nav
key in: widget-at-the-nav
key out: widget-at-the-nav
key in: widget-at-the-nav
key out: widget-at-the-nav
key in: widget-at-the-nav
And I forgot to tell you guys that the function .doThisAfterAnEvent() (not the handler() function) has an AJAX call inside.
This question is a mess, so I'm only going to touch on the most recent edit, and the code it contains.
Your approach with masking the handler with an anonymous function is pretty much correct, it's just that your loop is messing up your lexical scope. The AJAX bit is a very important detail, because any AJAX calls are most likely going to operate long after you've looped, which means those callback functions will all reference the same final value of key.
You need to create a new scope where the key is 'locked in', so that the references are correct.
function Con () {}
Con.prototype.doThisAfterAnEvent = function (fn) {
// Fake some AJAX programming
window.setTimeout(function () {
fn.call(this);
}, 1500);
};
$.fn.transform = function () {
return new Con();
};
var widgets = {
"widget-at-the-nav": $("#nav-widget").transform(),
"widget-at-the-footer": $("#nav-footer").transform(),
"widget-at-the-search": $("#nav-search").transform()
};
var handler = function(_args, _option) {
console.log("key in: " + _option);
// Other processes...
};
for (var key in widgets) {
console.log("key outer: " + key);
(function (key) {
// Use an IIFE to establish a new lexical scope
widgets[key].doThisAfterAnEvent(function(json) {
console.log("key out: " + key);
handler(json, key);
});
}(key));
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
In ES6, we'd use let.
If you ask that, I guess you mean at the moment you call doThisAfterAnEvent, you already know one parameter over two for your handler.
In this case, the solution is too wrap your handler with two parameter in a anonymous function that only take one parameter and then call back your handler :
x.doThisAfterAnEvent(function(_arg) { handler(myKnownArg, _arg) });

why doesn't JavaScript's setTimeout slow down keydown rate?

Why doesn't the following keydown event slow down by 3000 mil's, when I am continuously pressing the keydown event (letter k)? If I keep my finger down, the count rapidly adds up as through there is no setTimeout on mcount. why is that? There should be a delay between each count but I can't get it to work...
var mcount = 0;
function playershoot() {
if(!game.playerHit){
$(document).keydown(function(e){
switch(e.keyCode){
case 75:
clearTimeout();
setTimeout(console.log(mcount++), 3000);
break;
}
});
}
}
playershoot();
Any advice will be appreciated!
Thanks
1.: setTimeout() returns a timeoutId which can be cleared with clearTimeout(timeoutId). You're not doing that... so, after your 3 second delay, all those timeouts are called back-to-back.
2.: your console.log is executed immediately because you didn't wrap it in a function like so:
setTimeout(function() { console.log(mcount++) }, 3000);
setTimeout does not cause a delay, it starts a timer that fires an event after the specified amount of time.
You cannot "sleep" in Javascript, you need to refactor your code so it can work with events. For your code, it looks like you will need to set a flag at first keypress. Then return, and only allow new keypresses (ie. only respond to), when the flag is cleared. The flag can then be cleared automatically after a time with setTimeout.
To go with what #Norguard said, here's an implementation: http://jsfiddle.net/apu3P/
this.fire = function(){
var cFire = new Date();
if ((cFire - lastFire) / 1000 > 1/me.fireRate){
// code to fire the projectile
lastFire = cFire;
}
};
I have fireRate set up as an integer indicating how many times per second the player can fire.
In the demo, I set up 3 players each with different fire rates. If you hold the spacebar down, you can see this in action.
While everyone here is right, what they're missing is that you need to put a delay on the firing, not on the event being called...
Inside of your keydown event, set a timestamp, have a previous-time and a current-time for the event.
Inside of the function, have a time_limit.
So when you press the key (or it fires repeatedly), check for:
current_time - last_fired >= rate_limit;
If the current time is more than 3000ms since the last shot, then set the last_fired timestamp to the current time, and fire your weapon.
EDIT
Consider this trivial example:
var Keyboard = {};
var player = (function () {
var gun = {
charging : false,
lastFired : 0,
rateLimit : 3000
},
controls = { shoot : 75 },
isHit = false,
public_interface;
function shoot () {
var currentTime = Date.now();
if (gun.rateLimit > currentTime - gun.lastFired) { return; }
/* make bullet, et cetera */
gun.lastFired = currentTime;
}
function update () {
if (Keyboard[controls.shoot] || gun.charging) { this.shoot(); }
// if key was released before this update, then the key is gone...
// but if the gun was charging, that means that it's ready to be fired
// do other updates
}
function draw (ctx) { /* draw player */ }
public_interface = {
shoot : shoot,
damage : function (amt) { isHurt = true; /* rest of your logic */ }
draw : draw,
update : update
};
return public_interface;
}());
document.addEventListener("keydown", function (e) {
// if key is already down, exit
if (!!Keyboard[e.keyCode]) { return; }
// else, set the key to the time the key was pressed
// (think of "charging-up" guns, based on how long you've held the button down)
Keyboard[e.keyCode] = e.timeStamp;
});
document.addEventListener("keyup", function (e) { delete Keyboard[e.keyCode]; });
Inside of your gameloop, you're now going to do things a little differently:
Your player is going to update itself.
Inside of that update, it's asking the Keyboard if it's got the shoot key pressed down.
If it is, then it will call the shoot method.
This still isn't 100% correct, as Player shouldn't care about or know about Keyboard.
It should be handled through a service of some kind, rather than asking for window.Keyboard.
Regardless...
Your controls are now wrapped inside of the player -- so you can define what those controls are, rather than asking by keyCode all over the place.
Your events are now doing what they should: setting the key and going away.
In your current iteration, every time the browser fires keydown, which might be 300x/sec, if it wanted to, that event ALSO has to call all of your player logic... 300x/sec...
In larger games, you could then take this a step further, and make components out of Controls and Health, each having all of the properties and all of the methods that they need to do their own job, and nothing else.
Breaking the code up this way would also make it dirt-simple to have different guns.
Imagine an Inventory component:
The inventory contains different guns.
Each gun has its own rateLimit, has its own lastFired, has its own bulletCount, does its own damage, and fires its own bulletType.
So then you'd call player.shoot();, and inside, it would call inventory.equipped.shoot();.
That inner function would take care of all of the logic for firing the equipped gun (because you'd inventory.add(Gun); to your guns, and inventory.equip(id); the gun you want.
You have to pass returned value of setTimeout to clearTimeout . to cancel it.
var mcount = 0,timeout;
function playershoot() {
if(!game.playerHit){
$(document).keydown(function(e){
switch(e.keyCode){
case 75:
clearTimeout(timeout );
timeout = setTimeout(function(){
console.log(mcount++);
}, 3000);
break;
}
});
}
}
playershoot();

how to bind key combination ctrl+x+return in jquery

Is there any way to catch key combination ctrl+x+return in jquery(or javascript), such that if user presses this key combination, a function is called. I tried using the jquery hotkeys plugin, but that didn't worked.
You may find using KeyboardJS to be a better solution. Its dam simple to use. Here are docs;
KeyboardJS.on('ctrl + x + enter', function() {
//do stuff on press
}, function() {
//do stuff on release
});
Also, if you want to force pressing ctrl before x or enter you can do this instead
KeyboardJS.on('ctrl > x + enter', function() {
//do stuff on press
}, function() {
//do stuff on release
});
http://robertwhurst.github.com/KeyboardJS/
You can use the ctrlKey property of the key press event object
$(document).keypress(function(e) {
if(e.ctrlKey) {
// do code to test other keys
}
});
Also shift, alt reserved keys have properties, but the enter key has keyCode 13.
Is there any reason you can't to try a different combination - e.g. ctrl-alt-x? Enter key is usually reserved for triggering form submits and other events on a web page.
Use a global boolean array var keys = [] to check whether a key is pressed. Then use the following function to add a global hotkey:
window.addGlobalHotkey = function(callback,keyValues){
if(typeof keyValues === "number")
keyValues = [keyValues];
var fnc = function(cb,val){
return function(e){
keys[e.keyCode] = true;
executeHotkeyTest(cb,val);
};
}(callback,keyValues);
window.addEventListener('keydown',fnc);
return fnc;
};
As you can see it adds a new listener to the 'keydown' event. This listener will first set the corresponding value in keys true and then execute a test, whether the given keyValues are currently true. Note that you cannot remove keys[e.keyCode] = true and put it in another listener because this could result in a wrong callback order (first hotkey testing, then key mapping). The executeHotkeyTest itself is very easy too:
window.executeHotkeyTest = function(callback,keyValues){
var allKeysValid = true;
for(var i = 0; i < keyValues.length; ++i)
allKeysValid = allKeysValid && keys[keyValues[i]];
if(allKeysValid)
callback();
};
At last you have to add another listener to keyup to clean the released keys from keys.
window.addEventListener('keyup',function(e){
keys[e.keyCode] = false;
});
Now you can add a hotkey to ctrl+x+enter by using addGlobalHotkey(callback,[13,17,88]):
addGlobalHotkey(function(){
document.body.appendChild(
document.createElement('div').appendChild(
document.createTextNode('Ctrl + x + Enter down')
).parentNode);
},[88,13,17]);
JSFiddle demo
Instead of adding a listener for every hotkey you can use a global [[callback1,values1],[callback2,values2],...] array.
Important note: in IE prior version 9 you have to use attachEvent instead of addEventListener. Since you're already using jQuery you could use .on(...) or .keydown instead.
$("body").bind("keydown",keyDown);
function keyDown(e){
if((e.ctrlKey)&&(e.keyCode == 88)&&(e.keyCode == 13)){
alert("Keys down are Ctrl + x + Return");
}
}
simply just type like this:
document.getElementById('yourelementid').onkeydown = function(){
if(event.ctrlKey===true && event.keyCode==88){
//your code goes here
}
}
//no depedency needs. pretty straight-forward

Categories

Resources