How to disable repetitive keydown in JavaScript [duplicate] - javascript

This question already has answers here:
Prevent JavaScript keydown event from being handled multiple times while held down
(8 answers)
Closed 6 years ago.
I have a same problem like this guy How to disable repetitive keydown in jQuery , it's just that I'm not using jQuery so I'm having hard time translating it to "pure" JavaScript, anyways I have set switch-case of some keys and when I press and hold right arrow key my div is flying. Also if it's not a problem can you tell me what would be the easiest way to stop div movement when I let the right arrow key go, do I have to make a new switch case with clearInterval or?
switch (keyPressed) {
case 39:
setInterval(movFwr, 50);
break;
}
function movFwr() {
if (currPos == 1000) {
a.style.left = 1000 + "px";
} else currPos += 10;
a.style.left = trenutnaPozicija + "px";
}
I'm sorry guys, I've been busy a bit, I'm testing all possibilities and so far I have seen some interesting suggestions. I'll test em all these days and then rate of what ever is that you do on this site. Great community I must say. Thank you all for your help :)

Something like this should do the trick;
var down = false;
document.addEventListener('keydown', function () {
if(down) return;
down = true;
// your magic code here
}, false);
document.addEventListener('keyup', function () {
down = false;
}, false);

I would record the time and prevent action unless enough time has passed, for example using Date.now
var lastPress = 0;
function myListener() {
var now = Date.now();
if (now - lastPress < 1000) return; // less than a second ago, stop
lastPress = now;
// continue..
}
This can be made into a more generic function
function restrict(func, minDuration) {
var lastPress = 0;
return function () {
var now = Date.now();
if (now - lastPress < minDuration) return; // or `throw`
lastPress = now;
return func.apply(this, arguments);
};
}
// now, for example
foo = function () {console.log('foo');};
bar = restrict(foo, 200); // only permit up to once every 200ms
bar(); // logs "foo"
bar(); // void as less than 200ms passed

To ignore key events caused by key repeats, keep track of which keys are pressed during the keydown and keyup events.
var pressedKeys = [];
function keydownHandler(e) {
var isRepeating = !!pressedKeys[e.keyCode];
pressedKeys[e.keyCode] = true;
switch (e.keyCode) {
case !isRepeating && 39:
// do something when the right arrow key is pressed, but not repeating
break;
}
}
function keyupHandler(e) {
pressedkeys[e.keyCode] = false;
}
To stop your div moving, you could keep track of the interval ids in an array, and clear the interval during the keyup event with something like clearInterval(intervalIds[e.keyCode]), but I'd probably switch to using setTimeout() and checking whether they key is down instead. That way, you don't have to keep track of another variable.
var pressedKeys = [];
function keydownHandler(e) {
var isRepeating = !!pressedKeys[e.keyCode];
pressedKeys[e.keyCode] = true;
switch (e.keyCode) {
case !isRepeating && 39:
movFwr();
break;
}
}
function keyupHandler(e) {
pressedkeys[e.keyCode] = false;
}
function movFwr() {
if (pressedKeys[39] && currPos < 1000) {
currPos += 10;
a.style.left = currPos + "px";
setTimeout(movFwr, 50);
}
}
This way, you also automatically stop repeating the function as soon as the div reaches the right edge, instead of waiting for the user to release the arrow key.

Track the last key pressed, if its the same as current ignore it by returning, and use clearInterval to stop the intervals
//global variables
var lastKey = 0;
var moveTimer = [];
//in keydown function
if(lastKey == keyPressed)
return;
switch (keyPressed) {
case 39:
lastKey = keyPressed
moveTimer[keyPressed] = setInterval(movFwr, 50);
break;
}
//in a onkey up function
lastKey = null;
if(typeof(moveTimer[keyPressed]) != "undefined")
clearInterval(moveTimer[keyPressed]);

Related

clearInterval is not working on keydown addEventListener inside setInterval in javascript

This code will work fine when you press ArrowRight key in once at a time. But When you press ArrowKey twice or many at a time then the interval execute with infinity, clearInterval and if condition will not work. please help. Must run this code in your pc. then you will understand what I want to say.
var increaseNum;
function endLevel() {
window.clearInterval(increaseNum);
}
var n=0;
window.addEventListener("keydown", (e)=>{
if(e.key == "ArrowRight"){
increaseNum = setInterval(()=>{
n +=1;
if(n>10){
endLevel();
}
console.log(n);
}, 50)
}
})
Can't you do this:
window.addEventListener("keydown", (e) => {
if (e.key == "ArrowRight") {
function endLevel() {
window.clearInterval(increaseNum);
}
var increaseNum;
var n = 0;
increaseNum = setInterval(() => {
n +=1;
if(n>10){
endLevel();
}
console.log(n);
}, 50)
}
})
Because as you have it now, when you press multiple times increaseNum gets reassigned, hence clearInterval will not work as you expect. Also all those intervals will be increasing the same n.
In the above example, multiple intervals will fire now, each closing over its own version of n, increaseNum, endLevel. Not sure if that's what you want though.
Or you may find more suitable to not start a new interval if there is another already running. Or you may clear existing interval (as in the other answer). Depends on your use case.
you can just check if there is already a count in progress and if there is one don't start another, something like this:
var increaseNum;
function endLevel() {
window.clearInterval(increaseNum);
increaseNum = null;
n = 0;
}
var n = 0;
window.addEventListener("keydown", (e) => {
if (increaseNum) return;
if (e.key == "ArrowRight") {
increaseNum = setInterval(() => {
n += 1;
if (n > 10) {
endLevel();
}
console.log(n);
}, 50);
}
});
If I understand what you are wanting to do as while the user is pressing the right arrow then you want the game to continue but if they stop then the setInterval will reach n>10 and the endLevel method will be called.
let increaseNum;
let n = 0;
let reset = true;
function endLevel() {
n = 0;
window.clearInterval(increaseNum);
}
window.addEventListener("keydown", (e)=>{
if(e.key == "ArrowRight"){
window.clearInterval(increaseNum);
n = 0;
increaseNum = setInterval(()=>{
if(n>10){
endLevel();
}
n++;
}, 50)
}
})

How to setInterval once inside foreach using javascript

I need to set interval once for each slider element inside array when "if" statement is true but my code sets unlimited interval every time "if" statement is true. it is my js code:
const sliders = [realestateSlider,carsSlider,spectechnicSlider,motorcyclesSlider,partsSlider,beautySlider,clothesSlider,bestsellersSlider,topproductsSlider,salesSlider];
function checkIfIntoView(sliders){
sliders.forEach(function(slider,index){
if(slider.getBoundingClientRect().top <= window.innerHeight || document.documentElement.innerHeight){
setInterval(function(){slider.scrollLeft += scrollWidth;},1000);
}
});
}
window.addEventListener('wheel',function(event){ checkIfIntoView(sliders); });
Since you call the checkIfIntoView function in event listener, event listner will tiger that function each and every time a on wheel event occur. So if you want to call the checkIfIntoView function once the wheel event triggered, use a Boolean variable.
let ran = false;
function checkIfIntoView(sliders){
if (!ran) {
ran = true
sliders.forEach(function(slider,index){
if(slider.getBoundingClientRect().top <= window.innerHeight || document.documentElement.innerHeight){
setInterval(function(){slider.scrollLeft += scrollWidth;},1000);
}
});
}
}
Assuming that those are all Elements in the sliders Array, maybe you're looking to do something like:
const sliders = [realestateSlider, carsSlider, spectechnicSlider, motorcyclesSlider, partsSlider, beautySlider, clothesSlider, bestsellersSlider, topproductsSlider, salesSlider];
onscroll = e=>{
requestAnimationFrame(()=>{
let b, t, l;
for(let n of sliders){ // n is each node
b = n.getBoundingClientRect(); t = b.top; l = b.left;
if(t+b.height > -1 && t <= innerHeight && l+b.width > -1 && l <= innerWidth){
console.log('in view');
}
else{
console.log('not in view');
}
}
});
}
Note that you can always leave window. off of any property of window.
I'm not sure exactly what you are asking... But if you want to scroll until slider.getBoundingClientRect().top >= window.innerHeight then you could write your function like this:
const sliders = [realestateSlider,carsSlider,spectechnicSlider,motorcyclesSlider,partsSlider,beautySlider,clothesSlider,bestsellersSlider,topproductsSlider,salesSlider];
function checkIfIntoView(sliders){
sliders.forEach(function(slider,index){
let interval = setInterval(function(slider){
if (slider.getBoundingClientRect().top <= window.innerHeight || document.documentElement.innerHeight) {
slider.scrollLeft += scrollWidth;
} else {
clearInterval(interval);
}
}, 1000);
}
window.addEventListener('wheel',function(event){ checkIfIntoView(sliders); });
That way the interval will be cleared after the criteria is met. Keep in mind setInterval will continue to run until it is cleared via clearInterval

Rotation tween stop and tween start on mouse down

I have an object rotating on z axis. onMouseDown the rotation stops and onMouseUp the rotation resumes after 2 seconds.
Is there a way for the rotation to resume slowly (like ease in for css)?
The code for the rotation start and stop.
if ( etaj1 !== undefined ) {
etaj1.rotation.z += delta * 0;
if(!isMouseDown){
etaj1.rotation.z += delta * 0.05 ;
}
}
Also there is a resetTimer condition if a new click is made before the 2 seconds have passed.
var timerHandle = setTimeout (wait , 2000);
function resetTimer () {
window.clearTimeout (timerHandle);
timerHandle = setTimeout (wait , 2000);
}
function onMouseDown(){
console.log("Mouse DOWN");
isMouseDown = true;
}
function onMouseUp(){
window.clearTimeout (timerHandle);
timerHandle = setTimeout (wait , 2000);
}
function wait (){
resetTimer;
console.log("Mouse UP");
isMouseDown = false;
}
the idea is to make a transition for isMouseDown = false; and another transition for isMouseDown = true;
Basically what i want to do is stop the rotation from the link below with a quart.out tween, wait 2 seconds and if no click resume rotation with a quart.in tween.
https://threejs.org/examples/#webgl_loader_collada
Thanks.
I'm not a three.js guy, however what you are asking is applicable in other areas as well. So I will put here a general solution. First the proof of concept:
https://jsfiddle.net/ibowankenobi/u4xwnLam/4/
I'll start declaring the globals I need, wrap them up as you want:
var rotation = 0;
var bar = document.getElementById("bar");
var container = document.getElementById("container");
I need to demonstrate this without three.js so I need to have a container and something to rotate so you do not need bar and container in your case. Rotation in your case is your etaj1.rotation.z
We first need a function that will map 0-1 to 0-1 again, but not linearly. These are called interpolations, there are many ones, cosine, this, that, most widely used one is a stitched and transformed x^3 and gives a "slow in slow out" affect:
function slowInSlowOut(t){
if(2*t<<0){
return 4*(t-1)*(t-1)*(t-1)+1;
} else {
return 4*t*t*t;
}
}
We have to do something with this function so I will write another animate
function changeSpeed(obj,newSpeed){
var oldSpeed = obj.__oldSpeed || 0;
var startTime;
newSpeed = newSpeed || 0;
obj.__currentFrame && window.cancelAnimationFrame(obj.__currentFrame);//cancel a previous changeSpeed if fired twice
obj.__currentFrame = window.requestAnimationFrame(anim);
function anim(t){
startTime = startTime || t;
var elapsed = t - startTime,
parametric = slowInSlowOut(elapsed/2000);
if(parametric>=1){
obj.__oldSpeed = newSpeed;
} else {
obj.__oldSpeed = newSpeed + (oldSpeed-newSpeed) * (1-parametric);
obj.__currentFrame = window.requestAnimationFrame(anim);
}
}
}
You provide this function an object, and it will attach it a proprietary __oldSpeed property, the newSpeed parameter is measured in means of degrees/17ms (because I am using requestAnimationFrame and that will fire each ~17ms). So in your case the obj is your etaj1.
Within this function you can already update your etaj1.rotation.z if you want. But I will write a third function to fire continuously and listen to __oldSpeed property and take action based on that:
function animate(obj){
rotation += (obj.__oldSpeed || 0);
rotation = rotation % 360;
obj.style.transform = "rotate("+rotation+"deg)";
window.requestAnimationFrame(function(){animate(obj)});
}
Again here, obj is your etaj1 and rotation is etaj1.rotation.z. I am using DOM here, so I need to use the styles. You can adapt it to your case.
Animate won't fire, so we need to fire him:
window.requestAnimationFrame(function(){animate(bar)});
I'll add the eventListeners as you described:
container.addEventListener("mousedown",function(){changeSpeed(bar,0)},false);
container.addEventListener("mouseup",function(){changeSpeed(bar,12)},false);
Now we give it a starting speed:
changeSpeed(bar,12);
There you go. In case you wondered how to do it yourself, this might get you started. I guess people using other technology stacks can also adapt this to their case.
PS:
Someone mentioned a delay, in that case modify the changeSpeed a bit:
function changeSpeed(obj,newSpeed,delay){
delay = delay || 0;
var oldSpeed = obj.__oldSpeed || 0;
var startTime;
newSpeed = newSpeed || 0;
obj.__currentFrame && window.cancelAnimationFrame(obj.__currentFrame);
obj.__currentFrame = window.requestAnimationFrame(anim);
function anim(t){
startTime = startTime || t;
var elapsed = t - startTime;
if(elapsed < delay) {
return obj.__currentFrame = window.requestAnimationFrame(anim);
}
var parametric = slowInSlowOut((elapsed-delay)/2000);
if(parametric>=1){
obj.__oldSpeed = newSpeed;
} else {
obj.__oldSpeed = newSpeed + (oldSpeed-newSpeed) * (1-parametric);
obj.__currentFrame = window.requestAnimationFrame(anim);
}
}
}
Here is a fiddle that has a 2 second delay:
https://jsfiddle.net/ibowankenobi/u4xwnLam/10/

Return value from function whilst using setTimeout

Is there any way to have a function run with a setTimeout and also return a value assigned to a variable?
An example of what I mean is like this:
function count(start, target) {
if (start < target) {
start += 0.1;
return start;
} else if (start > target) {
return target;
}
// i'm fully aware the return above will prevent this line occur
// this is my issue currently
setTimeout(function () {
count(start, target)
}, 1000);
}
var el = document.getElementById('test');
var value = count(0.0, 1.0); //get the opacity value
function check() {
el.style.opacity = value; //assign what the value is in this moment
if (value != 1.0) {
setTimeout(check, 0);
}
}
check();
I know this code won't work the way i want it because return exit's the function, I wrote it to explain what I am trying to do.
The reason I want to do this in this kinda way is because I have an element which i want to fade in by altering it's opacity.
So i have a function which would increment a start value to a target value using what ever easing i want, this then returns said value which would be assigned to the element's opacity property.
I don't want to pass the element to this count function because that means it limits the use of it for when i want to animate other things besides elements.
What would the solution to this problem be?
I think what you are trying to do is to call the count method again if the value passed for start and target are not the same if so
function count(start, target) {
var ret;
if (start < target) {
ret = start + 0.1;
} else if (start > target) {
ret = target;
}
if (start != target) {
setTimeout(function () {
count(ret, target)
}, 1000);
}
return ret;
}

How to disable a button with a timed loop?

i'm trying to disable a button for 6 seconds while in a loop but so far I can't quite figure it out.
var disabledStartTimer = setInterval(disabledTimer, 1000);
function disabledTimer() {
var start = 0;
if (start > 6) {
clearInterval(disabledStartTimer);
console.log("disabled timer stopped");
attack.disabled = true;
} else {
attack.disabled = false;
start++;
};
}
attack = the button I click to attack.
var start = 0;
if (start > 6){
Clearly this will always go into the else. You set the variable to 0 and then test if it's greater than 6... it isn't. You likely wanted this to be a global, move it outside of the function.

Categories

Resources