I am working on a little HTML/JavaScript/CSS3 project for fun. I'm basically trying to make a wheel that rolls around in the browser window. To control the wheel I'm using keyup and keydown events for the cursor keys (left and right turn the wheel and up and down roll it forward or backward).
I've got it working pretty well so far, but there are two major glitches. Say I want to roll the wheel forward and without stopping I want to turn it a little to the right, then I would keep the up key pressed and press the right cursor key. When I do this there's a pause in the movement before it registers both events and keeps rolling.
That's one of the problems, the main problem is that, once I've performed the previous action and then wheel is at a desirable angle, if I let go of the right cursor key the browser registers both keys as released and the wheel comes to a stand still. Here is a jsFiddle of what it looks like: http://jsfiddle.net/UKqwu/1/. I know the code is a mess but it's a work in progress/learning experience and I've only been programming for a month or so.
Anyways thanks for any help. It only works in Chrome at the moment as far is I know. Haven't really been bothered fixing compatibility issues at this stage.
So, what is happening is essentially a limitation built in by your operating system, but there is a simple work-around. First I'll explain the limitation, and then the work-around.
If you were to (in a text box) hold down the "j" button, first one "j" would appear, and then after a short delay many "j"s would appear "jjjjjjjjjjjjjjjjjjjj"
This is the same problem your experiencing. The event fires once, waits for a moment, and then fires many more times.
The solution, however is simple. Instead of having your wheel move when the events are fired... have it update constantly, and separately keep track of what keys are up or down.
The Key Handler would look something like this...
function KeyHandler() {
this.left = false;
this.right= false;
...
function onKeyDown(e) {
if (e.keyCode == 37) {
this.left = true;
}
...
}
function onKeyUp(e) {
if (e.keyCode == 37) {
this.left = false;
}
...
}
}
(you'd attach the key handler to the body or whatever element you wish)
Your wheel would have an update function that looked like...
wheel.update = function() {
// the wheel is turning left
if (wheel.keyhandler.left) {
// make the appropriate adjustments
}
}
and then the game would have something like this...
wheel = new Wheel;
setInterval(function() {
wheel.update();
},100);
That way your wheel will always be updating based on the current state of the keys, and you wont have to rely on the limitations of events that are firing. :)
Here's a snippet of a simple game I once wrote
//key codes
var KEY_LEFT = 37;
var KEY_RIGHT = 39;
var KEY_A = 65;
var KEY_D = 68;
var KEY_SPACE = 32;
var keys = {};
function onKeyDown(e)
{
e = e || window.event;
var key = window.event.keyCode || e.which; // ie
keys[key] = true;
}
function onKeyUp(e)
{
var key = window.event.keyCode || e.which; // ie
delete keys[key];
}
This keeps track of all current key states. Then your game "tick" is on a setTimeout() rather than moving on key events and checks for appropriate keys.
function gameTick()
{
// update paddle angles
if (keys[KEY_LEFT])
{
// move left
}
if (keys[KEY_RIGHT])
{
// move right
}
}
Here's the game;
the problem you are facing is because your code is meant to detect single key press while your game needs 2 key press detection.
put this line in loop over size of e. that will set all pressed keys as 1. currently its detecting only one key press and processing for one at a time.
keys[e.keyCode] = 1;
check out this thread and u might get what you need. though its jquery, it might help conceptually. m also working on this with js... if anything new comes up will share...
Detect multiple keys on single keypress event in jQuery
your code and concept is cool if you are really one month old in programming.
Related
I use the keypress event listener in a lot of animations in processing js and here is a problem that consistently plagues me. In this example I made, when I press the 'd' button, a ball moves to the right. Actually, the ball is stationary, and the ground moves left below it.
The problem is this: When I press and hold 'd', the ball lurches right at the correct speed almost instantly, but then stops for an instant, then reaches the correct speed and then continues at that speed for as long as I hold 'd'. I don't know why the ball doesn't hit that speed and stay there, but that is what I'd like it to do.
Here is a fiddle which may show what I mean:
https://jsfiddle.net/4s14wq4d/
The method is quite simple:
hill.prototype.moveRight = function() {
this.x -= 5;
};
And here is the event listener in action:
var myCanvas = document.getElementById("myCanvas");
myCanvas.addEventListener("keypress", functionMove);
function functionMove(e) {
if (e.keyCode == "100") {
for (var i = 0; i < hillArray.length; i++) {
hillArray[i].moveRight();
}
}
};
First off, you should not be adding your own event listeners. Processing gives you functions like keyPressed() and keyReleased(). Use those instead.
Secondly, what you probably want to do is create variables that keep track of whether a key is pressed or not. Something like this:
var wPressed = false;
var sPressed = false;
var aPressed = false;
var dPressed = false;
Then in the keyPressed() function, you would set the corresponding variable to true, and in the keyReleased() function, you'd set it to false.
Finally, in the draw() function, you'd check each of the variable and do the right thing. Something like this:
if(dPressed){
for (var i = 0; i < hillArray.length; i++) {
hillArray[i].moveRight();
}
}
(Side note: If you're moving all of the elements by the same distance, you might just want to use a single xOffset variable or something that you use, that way you don't have to loop over every item to move it individually.)
Shameless self promotion: I wrote a tutorial on user input (including the above approach) available here. It's for regular Processing, but all of the ideas are the same in Processing.js.
I noticed mousewheel event is happening multiple times in mac osx. Can be atributed to inertia feature.
Is there a way to fix this behaviour?
(self signed ssl no worries please!)
https://sandbox.idev.ge/roomshotel/html5_v2/
I'm using scrollSections.js https://github.com/guins/jQuery.scrollSections
And it uses mousewheel jquery plugin: https://github.com/brandonaaron/jquery-mousewheel
I'm seeing a lot of people having the same issue: https://github.com/brandonaaron/jquery-mousewheel/issues/36
There are some solutions but none works with scrollSections plugin.
Any ideas how to disable this inertia feature from JS?
My attempted fix:
// Fix for OSX inertia problem, jumping sections issue.
if (isMac) {
var fireEvent;
var newDelta = deltaY;
if (oldDelta != null) {
//check to see if they differ directions
if (oldDelta < 0 && newDelta > 0) {
fireEvent = true;
}
//check to see if they differ directions
if (oldDelta > 0 && newDelta < 0) {
fireEvent = true;
}
//check to see if they are the same direction
if (oldDelta > 0 && newDelta > 0) {
//check to see if the new is higher
if (oldDelta < newDelta) {
fireEvent = true;
} else {
fireEvent = false;
}
}
//check to see if they are the same direction
if (oldDelta < 0 && newDelta < 0) {
//check to see if the new is lower
if (oldDelta > newDelta) {
fireEvent = true;
} else {
fireEvent = false;
}
}
} else {
fireEvent = true;
}
oldDelta = newDelta;
} else {
fireEvent = true;
}
You can see fix implemented here: https://sandbox.idev.ge/roomshotel/html5_v2/ But it is a hit/miss.
The latest solution with timeouts had one major drawback: kinetic scrolling effect could last rather long (even 1s or so)... and disabling scrolling for 1-2 seconds wouldn't be the best decision.
Soooo, as promised, here's another approach.
Our goal is to provide one response for one user action, which in this case is scrolling.
What's 'one scrolling'? For the sake of solving this problem, let's say that 'one scrolling' is an event that lasts from the moment the page has started to move till the moment the movement has ended.
Kinetic scrolling effect is achieved by moving the page many times (say, every 20ms) for a small distance. It means that our kinetic scrolling consists of many-many little linear 'scrollings'.
Empirical testing has showed that this little 'scrollings' happen every 17-18ms in the middle of kinetic scroll, and about 80-90ms at the beginning and the end. Here's a simple test we can set up to see that:
var oldD;
var f = function(){
var d = new Date().getTime();
if(typeof oldD !== 'undefined')
console.log(d-oldD);
oldD = d;
}
window.onscroll=f;
Important! Every time this mini-scroll happens, scroll event is triggered. So:
window.onscroll = function(){console.log("i'm scrolling!")};
will be fired 15 to 20+ times during one kinetic scroll. BTW, onscroll has really good browser support (see compatibility table), so we can rely on it (except for touch devices, I'll cover this issue a bit later);
Some may say that redefining window.onscroll is not the best way to set event listeners. Yes, you're encouraged to use
$(window).on('scroll',function(){...});
or whatever you like, it's not the point of the problem (I personally use my self-written library).
So, with the help of onscroll event we can reliably say whether this particular mini-movement of the page belongs to one long-lasting kinetic scroll, or is it a new one:
var prevTime = new Date().getTime();
var f = function(){
var curTime = new Date().getTime();
if(typeof prevTime !== 'undefined'){
var timeDiff = curTime-prevTime;
if(timeDiff>200)
console.log('New kinetic scroll has started!');
}
prevTime = curTime;
}
window.onscroll=f;
Instead of "console.log" you can call your desired callback function (or event handler) and you're done!
The function will be fired only once on every kinetic or simple scroll, which was our goal.
You may have noticed that I've used 200ms as a criteria of whether it's a new scroll or a part of the previous scroll. It's up to you to set it to greater values to be 999% sure you prevent any extra calls. However, please keep in mind that it's NOT what we have used in my previous answer. It's just a period of time between any two page movements (whether it's a new scroll or a little part of a kinetic scroll). To my mind, there's a very little chance that there will be a lag more than 200ms between steps in kinetic scroll (otherwise it will be not smooth at all).
As I've mentioned above, the onscroll event works differently on touch devices. It won't fire during every little step of kinetic scroll. But it will fire when the movement of the page has finally ended. Moreover, there's ontouchmove event... So, it's not a big deal. If necessary, I can provide solution for touch devices too.
P.S. I understand that I've written a bit too much, so I'd be happy to answer all your questions and provide further code if you need one.
Provided solution is supported in all browsers, very lightweight and, what's more important, is suitable not only for macs, but for every device that might implement kinetic scrolling, so I think it's really a way to go.
You know, I think it's a better idea to use timeouts in this case. Why not write something like this:
// Let's say it's a global context or whatever...:
var fireEvent = true;
var newDelta, oldDelta, eventTimeout;
newDelta = oldDelta = eventTimeout = null;
// ... and the function below fires onmousewheel or anything similar:
function someFunc(){
if(!fireEvent) return; // if fireEvent is not allowed => stop execution here ('return' keyword stops execution of the function), else, execute code below:
newDelta = deltaY;
if(oldDelta!=null&&oldDelta*newDelta>0){ // (1.1) if it's not the first event and directions are the same => prevent possible dublicates for further 50ms:
fireEvent = false;
clearTimeout(eventTimeout); // clear previous timeouts. Important!
eventTimeout = setTimeout(function(){fireEvent = true},500);
}
oldDelta = newDelta;
someEventCallback(); // (1.2) fire further functions...
}
So, any mousewheel event fired within half a second after any previous mousewheel event call will be ignored, if it is made in the same direction as previous (see condition at 1.1). It will solve the problem and there's no way user would spot this. Delay amount may be changed to better meet your needs.
The solution is made on pure JS. You're welcome to ask any questions about integrating it in your environment, but then I'll need you to provide further code of your page.
P.S. I have not seen anything similar to eventCallback() call in your code (see 1.2 of my solution). there was only fireEvent flag. Were you doing something like:
if(fireEvent)
someEventCallback();
later on or something?
P.P.S.note that fireEvent should be in global scope in order to work here with setTimeout. If it's not, it's also quite easy to make it work fine, but the code needs to be altered a bit. If it's your case, tell me and I'll fix it for you.
UPDATE
After a brief search I found out, that similar mechanism is used in Underscore's _debounce() function. See Underscore documentation here
Have you though about using fullpage.js instead?
It has a delay between arriving to a section and the moment you are able to scroll to the next section which solves part of the problem Mac users experience with track-pads or Apple magic mouses.
It would also provide you some other benefits, such as much more options, methods and compatibility with touch devices and old browsers with no CSS3 support.
To have something to start with, let's make your solution shorter (therefore easier to understand & debug):
var fireEvent;
var newDelta = deltaY;
var oldDelta = null;
fireEvent = EventCheck();
oldDelta = newDelta;
function EventCheck(){
if(oldDelta==null) return true; //(1.1)
if(oldDelta*newDelta < 0) return true; // (1.2) if directions differ => fire event
if(Math.abs(newDelta)<Math.abs(oldDelta)) return true; // (1.3) if oldDelta exceeds newDelta in absolute values => fire event
return false; // (1.4) else => don't fire;
}
As you see, it does absolutely what your code does.
However, I can't understand this part of your code (which corresponds to (1.3) in my snippet):
//check to see if the new is lower
if (oldDelta > newDelta) {
fireEvent = true;
} else {
fireEvent = false;
}
from code provided it's unclear how deltaY is calculated. As one could assume, delta equals to endPosition - initialPosition. So, oldDelta>newDelta does not mean that the new position is lower, but that the new gap between these two values is bigger. If it's what it mean and you still use it, I suppose you try to track inertia with that. Then you should alter comparative operator (use less than, instead of greater then and vice-versa). In other words, I'd write:
if(Math.abs(newDelta)>Math.abs(oldDelta)) return true; // (1.3)
you see, now I've used 'greater than' operator, which means: newDelta exceeds oldDelta in absolute values => it's not inertia and you can still fire the event.
Is it what you're trying to achieve or have I misinterpreted your code? If so, please explain how deltaY is calculated and what was your goal by comparing old&new Deltas.
P.S. I'd suggest not to use if(isMac) in this step, while a problem can also potentially hide there.
I am building an small CAD app that allows the user to select items and then press LEFT/RIGHT arrow keys to move them.
The problem
The problem is that the function that moves the elements takes too long to process if my canvas has many elements drawn on it.
The worst is that if the user presses and holds the arrow keys, the function needs to get fired so many times that I get the dreaded Unresponsive Script alert from my browser.
Even if the user burst-fires a keypress instead of holding it, I still have a problem.
So I figured that the way to go, is:
Collect keypresses and after e.g 2000ms to fire up the function with
the appropriate keypressCounter - the number of keypresses collected
before keyUp
The problem with the method above:
The user will have to wait for the timeout even if just one keypress was made - which does not have a huge overhead - therefore the ''crude'' method described above is less than ideal. It will make the nudge functionality feel cludgy where it could be fast.
What would be the most correct way to go around this? Any suggestions are welcome since the solution I suggest above seems rather crude
Notes:
I would appreciate code snippets, even rudimentary ones. I'm still in
my early coding stages. Or at least a comprehensive explanation to
any proposed solutions.
I am using Paper.js as the canvas library to draw the items, but that
shouldn't be a factor in any solution
The code this far:
//Keybinding ''right'' arrow key - Fires up the function to nudge left.
$(document).keydown(function (e) {
if (e.which === 39) {
nudgeSelection("right");
}
});
//Function that moves the selected element(s). Accepts direction as parameter.
function nudgeSelection(direction) {
var selected = paper.project.selectedItems;
for (var i = 0; i < selected.length; i++) {
switch (direction) {
case "up":
selected[i].position.y = selected[i].position.y - 1;
break;
case "down":
selected[i].position.y = selected[i].position.y + 1;
break;
case "left":
selected[i].position.x = selected[i].position.x - 1;
break;
case "right":
selected[i].position.x = selected[i].position.x + 1;
break;
}
}
}
Since my other bug got solved, I'm posting a new question for this bug.
I made a Snake canvas game, but my snake tends to eat itself when you press two buttons at the same time.. I'm not sure how to explain it properly, but this is what happens:
Say my snake is moving towards the left, and I press down + right, it'll eat itself and trigger a game over. Same when it goes to the right: down + left and bam, dead. I can't seem to reproduce the bug when the snake goes up and down though..
This is the code involved with changing directions:
bindEvents = ->
keysToDirections =
37 : LEFT
38 : UP
39 : RIGHT
40 : DOWN
$(document).keydown (e) ->
key = e.which
newDirection = keysToDirections[key]
if newDirection
setDirection newDirection
e.preventDefault()
setDirection = (newDirection) ->
# TODO: there's some bug here; try pressing two buttons at the same time..
switch Snake.direction
when UP, DOWN
allowedDirections = [LEFT, RIGHT]
when LEFT, RIGHT
allowedDirections = [UP, DOWN]
if allowedDirections.indexOf(newDirection) > -1
Snake.direction = newDirection
I thought there was something wrong with the compiled JS because my switch statement doesn't have a break on the last case; however, I tried adding else return to the coffee script file and this didn't change anything. I'm completely lost here so I hope someone will be able to spot where I'm going wrong.
It seems as if it takes the keydown events right, but they get overridden when you press too fast. If that makes sense? Like.. You'd press up when the snake is going right, but then you press left before it actually had a chance to go up and then it just goes left. Chaotic sentence right there, I suggest you play for a while and try to reproduce this if you're as intrigued as I am :(
I have no clue how to debug this properly.. The game loop tends to spam me with messages when I do console.log.
A live demo and link to my github repo can be found here
Thanks in advance.
The problem is that if you push the keys quickly enough, its possible to trigger the event callback multiple times during one frame. Thus, if the snake is going down, it can turn right and then up in the same frame, thus reversing direction and eating itself. I'll suggest two ways to solve this problem:
The first is to set a flag when the direction is changed, i.e.:
if allowedDirections.indexOf(newDirection) > -1 and !turnedThisFrame
Snake.direction = newDirection
turnedThisFrame = true
and then, in your code that runs every frame, set turnedThisFrame to false.
The second is to rework how you deal with keypresses. This is often the approach that I take. Keep a map of which keys are pressed (say, keysDown), and bind a function that sets keysDown[e.which] = true to keydown and another function which sets keysDown[e.which] = false to keyup. Then, you can check which keys are pressed in the code that runs each frame, and act accordingly.
Here's some details on how I implemented the second solution in a current project. The following code appears in my onload handler:
do ->
keysDown = []
$(document).keydown (e) ->
keysDown.push e.which
$(document).keyup (e) ->
keysDown = _.without keysDown, e.which
window.isPressed = (keyCode) -> keyCode in keysDown
The do -> construct is used to create a function and immediately calling it, which has the beneficial effect of keeping keysDown in closure for the handlers and isPressed, while avoiding polluting the main scope of the onload callback.
Then, at the beginning of my tick function (which runs once per frame and handles game logic, drawing the screen, etc.) I would have something like this:
switch Snake.direction
when UP, DOWN
allowedDirections = [LEFT, RIGHT]
when LEFT, RIGHT
allowedDirections = [UP, DOWN]
for direction in allowedDirections
if isPressed(directionToKey[direction])
Snake.direction = newDirection
break
The map directionToKey would just be the opposite of your keysToDirections.
Note that this means that keys listed first in allowedDirections will have priority, i.e. if you are going right and press both up and down in the same frame, up will occur regardless of which was pressed first.
Added advantage to this second method: you don't have to change the key handler callbacks when switching between, say, a menu screen and the game. You just have a different tick function checking for what is pressed.
You must retain the last direction is which the snake actually moved and determine allowedDirection based on that. Pressing a key only represents the intent to move in that direction, but it does not actually move when the key is pressed, but based on the speed of the game, i guess.
Your snake eating itself shows a problem with your hit detection (and hit detection handling) code. If you're hitting the snake, the game should end. A snake is not an apple! Nevermind, apparently I missed the part where the game is ending for you.
If you choose to allow pixel-granularity (I can't see your demo, my work's network is half-down..), you can't really make U turns "safe", like you could with a hex-granularity approach. Not saying your choice is bad, just telling you to pick your battles, some you just can't win.
I had the same problem, but I solved it by implementing a queue; each keypress sets the direction of the snake and that (new) direction is pushed to an array. Then, before I actually move the snake in the game loop, I check if there are any elements in my direction queue. If so, I shift it once via Array.shift. The return value is the first element of the queue and the new direction for my snake. Array.shift also removes that element from the queue, which is exactly what we want. If two keys are pressed almost simultaneously, the first and the second direction change is stored in our queue, and our aforementioned routine takes care of the rest, by first applying the first direction change in the next available tick and then applying the second direction change in the next tick thereafter.
Hope that makes sense? :-)
I know its possible but I cant seem to figure it out. In its basic form I have a canvas circle moving 5 pixels every key event however this seems to be rather slow at firing and really jumpy, plus it stalls when changing key or changing from rapid key press to holding down.
So can someone enlighten me of a way ofwhich this could work without it stuttering?
Thanks.
What you want to do is set variables on keydown and keyup to true or false that you check every tick. Something like this:
var tickRate = 30,
keyDown = {},
keyMap = {
37: 'left',
38: 'up',
39: 'right',
40: 'down'
};
$('body').keydown(function(e){ keyDown[keyMap[e.which]] = true; });
$('body').keyup(function(e){ keyDown[keyMap[e.which]] = false; });
var tick = function() {
if (keyDown['up']) {
// up code
} else if (keyDown['down']) {
// down code
} else if (keyDown['left']) {
// left code
} else if (keyDown['right']) {
// right code
}
// other code
setTimeout(tick, tickRate);
};
tick();
The main problem you have here is that you are at the mercy of the "key repeat" functionality of the Operating System. Usually if you hold a key down, a keyPress event will be generated once, then (after a delay) on a repeat. But if you want to control this delay/repeat, then that's not good enough.
Instead I'd have your own polling function, set to run every few milliseconds, checking whether each key you're interested in is down or up. Then you know that, for every poll whilst the key is down, some event can be fired regardless of the OS's settings.
You can then apply your own algorithms and multipliers to smooth the movement out.