I'm trying to develop a game using CraftyJS. I'm using
Crafty 0.7.1
Chrome 50.0.2661.94
Windows 10
A 2016 Dell XPS15.
I'm noticing some oddities in how keyboard events are being handled.
I'm guessing that a lot of this has to do with Chrome or maybe even
my physical keyboard, and that Crafty is only relevant in that I'm
using it's API.
First, here's my SSCCE. This code adds a key to the keys array when it's pressed, removes it when it's released, and logs out the array every second.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>keyTest</title>
<script src="../crafty.js"></script>
<script>
window.onload = function(){
Crafty.init(window.innerWidth, window.innerHeight, document.getElementById('game'));
var keys = [];
var keyDown = function(e){
console.log("KeyDown " + e.key);
keys.push(e.key);
};
var keyUp = function(e){
console.log("KeyUp " + e.key);
keys.splice(keys.indexOf(e.key), 1);
};
Crafty.e("Keyboard").bind("KeyUp", keyUp).bind("KeyDown", keyDown);
Crafty.e("Delay").delay(function(){console.log(keys)}, 1000, -1);
};
</script>
</head>
<body>
<div id="game"></div>
</body>
</html>
I'm noticing some odd behavior:
First, It looks like Crafty will only recognize four letter-key presses at once, unless they can be typed using both the right and left hand. For example holding down ASDFE at once results in: [65, 83, 68, 70] The E key remains unrecognized no matter how much I bash on it. But, if for example I hold down ASDFJKL, then I see: [65, 83, 68, 70, 74, 75, 76].
Only two arrow keys will be recognized at once unless the third one is the down arrow. For example pressing LEFT, UP, RIGHT results in: [37, 38] But LEFT DOWN RIGHT results in: [37, 39, 40]
What on earth is up with this? My guess is that someone between my fingers and my JS is trying to cleverly correct for errant key presses (typos), but I don't know who, and I don't know what rules govern this.
EDIT: I suspect that it's my keyboard itself (or the OS) that is failing to send these keyboard events. But I'm still looking for a good way to confirm this.
As #David has figured out, the issue is called keyboard ghosting:
"Ghosting" is the problem that some keyboard keys don't work when multiple keys are pressed simultaneously. The key presses that don't show up on the computer or seem to have disappeared are said to have been "ghosted". On most keyboards, even some that are explicitly marketed as "Anti-Ghosting," this happens with many three key combinations. Imagine playing your favorite video game and not being able to, say, run diagonally and fire your weapon at the same time (say pressing a, w, and g simultaneously). This is a result of the internal design of most existing keyboards...
Since not every consumer has a gaming keyboard (which particularly don't suffer from this issue), I guess the only thing you can do is to design your game around not requiring pressing three or more keys at the same time.
Related
I use Anki, a software of flashcards to learn a new language.
What my card looks like :
I want to use a key on my (physical) keyboard to show/hide additional infos on my card to review my cards faster on iPad.
The problem is that the document element is not focused at first (it is the answer buttons that are), so the keyup event in JS/JQuery doesn't work. I need to touch the iPad screen first to then use my keyboard if I want it to work, which kind of defeats the whole point.
My code :
$(document).ready(function() {
$(document).on('keyup', function(e) {
var key = e.key;
if (key == "p") {
// My Code
}
});
});
I tried $(document).click(), $(document).trigger("click") and $(document).focus() without success. Other people (on Reddit) have had this problem but resolved it with add-ons for Anki − As I would like to make it work on iPad (so no add-on), this is not an option forme sadly.
Any help would be greatly appreciated.
Cheers!
In my cocoa code, I’ve implemented a drag & drop for a file path. It is triggered not by a mouseDown, but as a result of a callback I receive from javascript code (after click-dragging an HTML object).
The problem is that it works only 80% of the time: I do the same click-drag operation over and over again and get two behaviors. In both, an icon representing the dragged file path begins following the cursor around (as expected).
Now, in most of the times the dragged file path is “accepted” by the receiver, an appropriate visual feedback is given during the drag, and eventually when the mouse is released – the file path is accepted and dropped.
However there are times where the file path is not “accepted” when hovering over the target. When this happens and the mouse is released, the icon slides back to its origin and the file path is not dropped onto the target.
Here’s my code (simplified), based on Apple’s docs:
void myInitiateDrag(const char * in_filepath, NSView * view)
{
NSString *nsfilepath = [NSString stringWithUTF8String:in_filepath];
NSEvent *theEvent = [[view window] currentEvent];
NSPoint dragPosition = [view convertPoint:[theEvent locationInWindow] fromView:nil];
NSRect aRect = NSMakeRect(dragPosition.x, dragPosition.y, 10, 10);
[view dragFile:nsfilepath fromRect:aRect slideBack:YES event:theEvent];
}
Using this exact code within mouseDown works perfectly fine, 100% of the times. However as I said, I don’t receive a mouseDown in my case.
Another observation: when the mouse button is released after a drag operation, mouseUp is not being called. This is regardless of where I initiate the drag operation, and is also mentioned in another SO question. So, I don’t know if this has to do with my problem.
Among the things I’ve done, I tried to find differences between the currentEvent when successful and when not successful, with no luck.
In the developer docs it explicitly says “A dragging session is initiated by the user clicking the mouse inside a window or view and moving the mouse [...] You invoke this method in the mouseDown: or mouseDragged: method of your subclass of NSView or NSWindow.”.
Is this mandatory? What is wrong with what I’m doing and what other options do I have?
Have the same issue, finally was able to make it work with event copy:
[ NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseDragged handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) {
[self.controller mouseEventGlobal:event];
return event;
}];
After that in the controller, I'm able to use beginDraggingSessionWithItems:event:source after I have callback from JS.
I want to load a very large HTML page containing nothing but the letter 'x' (let's say 10000 lines of each 100 characters), each of the characters linking to a similar url: the first one to www.example.com/1, the second to www.example.com/2, etc.
Of course I cannot just generate the entire page with php because it would build a very large file for the browser to download. But if I try it with javascript and a simple for-loop, it takes ages for the script to complete.
Any solutions for this problem?
Much better idea:
document.body.addEventListener("click", function (e) {
console.log("Clicked at coordinates ", e.pageX, e.pageY);
// Convert in your own fashion; here's one idea:
var index = (e.pageX % 100) + (100 * e.pageY);
window.location.href = "http://example.com/" + index;
});
Those are pretty much your only options. Only thing I think you can do is use a faster browser (test out the latest versions of the current browsers, recent years have seen lots of javascript optimization) and/or a faster computer.
The biggest issue though is why are you doing this? I can't think of any practical use for this, and most likely there's a better way to do what you're actually trying to accomplish
Even generating it server side, it'll likely take a long time to render. We've recently tried loading a page that hasn't needed paging previously because the dataset has always been small, but with a huge dataset even though the whole page was transferred, it still froze the entire browser for over 5 minutes before we had to kill the process. I suspect you'll run into similar issues if it's really that much stuff.
What you are trying to do is not very clever. 10,000 times 100 is 1,000,000 elements. The most efficient way is use P elements with a minimal id, then use a click listener on the body element to determine which element was clicked on and genrate the URL. So your html looks something like:
<html><title>Silly Page</title>
<style type="text/css">p {display:inline;margin:0;padding:0}</style>
<script type="text/javascript">
function doNav(e) {
var id = (e.target || e.srcElement).id;
window.location.href = 'http://www.example.com/' + id.replace('p','');
}
</script>
<body onclick="doNav(event)"><p id=p1>x<p id=p2>x<p id=p3>x<p id=p4>x...
</body>
If you are trying to associate a link with a location on a page, use an image map, it will be vastly more efficient.
You have two options:
To make a virtual view. In this case you
will load only visible elements and
do scrolling/panning manually
(similar to maps)
To output/populate just text
"xxxxx...." and handle clicks by
coordinates and synthesizing
hyperlink clicks by code. In this
case you will have only one DOM
element - container of x'es
The following chokes for about 20s in FF on my machine, about 6s in Chrome. A good portion of that is spent parsing the injected DOM, and I suspect this would remain the case for a downloaded DOM of the same structure. No matter how you approach this, it's going to foul up the user's browser for a while.
<script type="text/javascript">
//<![CDATA[
$(function() {
var strings=[];
for(var a=0;a<10000;a++)
{
for(var b=0;b<100;b++)
{
var c=a*100+b;
strings.push(
'x');
}
strings.push("<br/>");
}
var megaString=strings.join("");
$("body").html(megaString);
});
//]]>
</script>
I need to detect whether the user has pressed the dot key in the numeric keypad. I've written this first draft that works for me:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head><title></title>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript"><!--
(function($){
var knownCodes = [
[110, 46], // Firefox, IE, Chrome
[78, 46] // Opera
];
var pressedCodes = [null, null];
$.fn.comma = function(){
this.live("keydown", function(e){
pressedCodes = [e.which, null];
}).live("keypress", function(e){
pressedCodes[1] = e.which;
for(var i=0, len=knownCodes.length; i<len; i++){
if(pressedCodes[0]==knownCodes[i][0] && pressedCodes[1]==knownCodes[i][1]){
$("#log").append("<li>Decimal key detected</li>");
break;
}
}
});
return this;
};
$(function(){
$('<ol id="log"></ol>').appendTo("body");
});
})(jQuery);
jQuery(function($){
$(".comma input:text, .comma textarea").css("border", "1px solid black").comma();
});
//--></script>
</head>
<body>
<form action="" method="get" class="comma" size="20">
<p><input type="text"></p>
<p><textarea rows="3" cols="30"></textarea></p>
</form>
</body>
</html>
However, I can only test it in a Windows XP box with a Spanish keyboard. My questions are:
Is it safe to read from e.which? I'm using it because both e.keyCode and e.charCode return undefined in at least one browser.
Does the operating system affect these numeric codes in some manner or it's only a browser stuff?
Do these codes depend on the keyboard layout?
Background info: I couldn't find a jQuery plugin to remap numeric keypad so I'm writing my own.
Update
I'll explain my exact need. Spanish keyboards that have a numeric keypad feature a . key. However, the decimal separator in Spanish is ,. That makes it annoying to type numbers in web applications. Some desktop apps like MS Excel remap this key so it inserts a comma. I'm trying to mimic that.
I've adapted a little script I've been using to post it here. That's how I got the values for the knownCodes arrays:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head><title></title>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript"><!--
(function($){
$.fn.showKeyCodes = function(){
var log = function(e){
$("<li></li>").text(e.type + "(): [keyCode, charCode, which]=[" + e.keyCode + ", " + e.charCode + ", " + e.which + "]").appendTo("#log");
}
this.live("keydown", function(e){
log(e);
}).live("keypress", function(e){
log(e);
}).live("keyup", function(e){
log(e);
});
return this;
};
$(function(){
$('<ol id="log"></ol>').appendTo("body");
});
})(jQuery);
jQuery(function($){
$(".showKeyCodes input:text, .showKeyCodes textarea").css("border", "1px solid black").showKeyCodes();
});
//--></script>
</head>
<body>
<form action="" method="get" class="showKeyCodes" size="20">
<p><input type="text"></p>
<p><textarea rows="3" cols="30"></textarea></p>
</form>
</body>
</html>
Type the . key in your numeric keypad (or whatever key replaces it in your keyboard layout) and the key that corresponds to the same character in the alphabetical keypad. The goal is to detect you clicked the first one and not the second one.
Is it safe to read from e.which?
Normally in IE no, you only get keyCode (though on keypress it's actually a character code not a key code). Your code can read which because behind the scenes jQuery copies the keyCode value into which on IE. Without jQuery, you need property-sniffing code to know which one to pick. (You can't always read keyCode on keypress because it's not guaranteed to be provided and it's 0 on Firefox.)
Please note I don't want to detect a character: I want to detect a key.
You can't do that in keypress, which only gives you a character code (46, ASCII for .). You would instead have to trap keydown, which gives you access to a real key code (in the keyCode property, and also in browsers that aren't IE, in which).
The keyCode reported for the numeric keypad ./, button is 110 (ASCII for lower-case N). Except on opera where it's 78, ASCII for upper-case N and thus indistinguishable from pressing the N button.
So you could claim the keydown event and store a flag when key 110 or 78 was being depressed, then claim keypress. If the flag from the last keydown was set and the which (charCode) property is 46, return false to prevent the keypress going through, and then insert a , at the focus point instead. (This isn't quite reliable if there are multiple keys being pressed at once and autorepeat going on, though.)
Note that inserting a character at focus is itself non-trivial; see many previous questions here. You'll need branching code for IE (document.selection.createRange().text=...) and other modern browsers (changing the input's value based on selectionStart/selectionEnd). Older and mobile or niche browsers may not support either method of editing, so detect the lack of these properties and leave the keyboard alone if you can't do it.
Does the operating system affect these numeric codes in some manner or it's only a browser stuff?
Do these codes depend on the keyboard layout?
In general yes, though not in a way that will affect you here. Key events are in general still hugely variable cross-browser, cross-keyboard and cross-platform; they are best avoided if you can.
Personally I think the proper solution to a bad keyboard layout choice like Spanish's numpad decimal point would be to modify the layout using MSKLC.
Your code doesn't do anything on my end, so I'd say it probably does depend on keyboard layout. A quick check confirms that the output on my finnish qwerty keyboard is keycode 44, instead of the expected 46.
Some time after asking this question, I finally found a jQuery plugin that does exactly this: numpad-decimal-separator
So I have the following code, which should append 'true' to the div "test" every 25ms as long as key 68 (the d key) is being pressed, right?
<html>
<body>
<div id="test"></div>
<script type="text/javascript">
var key=false;
var keyDown=function(e) {
if (e.keyCode==68) {
key=true;
}
}
var keyUp=function(e) {
if (e.keyCode==68) {
key=false;
}
}
document.onkeydown=keyDown;
document.onkeyup=keyUp;
var run=function() {
document.getElementById('test').appendChild(document.createTextNode(key+'\n'));
t = setTimeout('run()', 25);
}
var t = setTimeout('run()', 25);
</script>
</body>
</html>
Save the code, load it in a browser and hold down on the d key. If I'm not crazy, you'll see that it occasionally appends 'false' even though the d key was never released. (I've tried this in FF and Chrome in Linux and Vista). Anybody happen to know why, or have a workaround?
Edit: It seems to behave as expected in FF running in OS X.
I just tried it in IE7. After changing "e.keyCode" to "event.keyCode", it worked just as you expected.
Have you actually tried the code you pasted here, or are you working with other code that may have a bug in it?
Edit
I just ran this in Chrome. Again, it behaves as expected.
Are you using a wireless keyboard that could be susceptible to interference or battery weakness that wouldn't affect normal keyboard use, but becomes visible in this test?