I'm playing around with some javascript and JQuery. I'm trying to do something specific each time the user scrolls. It works fine when I use a mouse with a scollwheel. But when I use the trackpad on my laptop it goes incredibly fast. I think it's because it executes the mousewheel function many times when scrolling. So I want to limit how often you can execute the function. Like with a 500ms delay between each call if the user keeps scrolling. It shouldn't delay the functions itself but run immediately and then wait 500ms before it can be executed again. Hope you understand.
Here's my code
$(window).bind('mousewheel', function(event) { // My mousewheel function.
if (event.originalEvent.wheelDelta >= 0) {
scroll(-1);
} else {
scroll(1);
}
});
/* Functions */
function scroll(dir) {
if (dir == -1) {
if (current > 0) {
current--;
}
} else {
if (current < list.size() - 1) {
current++;
}
}
var number = 100 * current;
var value = "translateY(-" + number + "vh)";
main.css("transform", value);
for (var i = 0; i < list.size(); i++) {
$('#nav li:nth-child(' + (i + 1) + ')').removeClass('active');
}
$('#nav li:nth-child(' + (current + 1) + ')').addClass('active');
}
You need to define a variable that indicate when to run the function or not. in this case is the "isScrolling" variable.
$(window).bind('mousewheel', function(event) { // My mousewheel function.
if (event.originalEvent.wheelDelta >= 0) {
scroll(-1);
} else {
scroll(1);
}
});
/* Functions */
var isScrolling = false; // This variable define when to run the function
function scroll(dir) {
if(isScrolling){
return;
}
isScrolling = true;
if (dir == -1) {
if (current > 0) {
current--;
}
} else {
if (current < list.size() - 1) {
current++;
}
}
var number = 100 * current;
var value = "translateY(-" + number + "vh)";
main.css("transform", value);
for (var i = 0; i < list.size(); i++) {
$('#nav li:nth-child(' + (i + 1) + ')').removeClass('active');
}
$('#nav li:nth-child(' + (current + 1) + ')').addClass('active');
setTimeout(function(){
isScrolling = false;
},500) // -> Here you can modify the time between functions call
}
What you need is a throttle function. This function wraps around other functions and checks the delay in ms and the id of the event.
/**
* #description delay events with the same id, good for window resize events, scroll, keystroke, etc ...
* #param {Function} func : callback function to be run when done
* #param {Integer} wait : integer in milliseconds
* #param {String} id : unique event id
*/
var throttle = (function () {
var timers = {};
return function (func, wait, id) {
wait = wait || 200;
id = id || 'anonymous';
if (timers[id]) {
clearTimeout(timers[id]);
}
timers[id] = setTimeout(func, wait);
};
})(),
You can use it like this:
$(window).on('mousewheel', function () {
throttle(function(){
var isScrollUp = event.originalEvent.wheelDelta >= 0 ? -1 : 1;
scroll(isScrollUp);
}, 500, 'my mousewheel event');
});
A nice example of underscore throttle -vs- debounce.
Related
I'm trying to make a small game with Javascript where the user has to enter the sum of two random numbers (a and b).When you click on a button or when you press enter, it calls a function which checks if the sum is the same as what you entered. If that's the case, the function play() is called again and a and b change. It works fine the first time, but the second time, unless the second sum is equal to the first one, it doesn't work. What does my answer() function acts as if a and b didn't change ?
let count = 0;
function play() {
if (count < 10) {
// setTimeout(loss, 30000);
count += 1;
document.getElementById("user-answer").value = "";
var a = Math.floor(Math.random() * 20) + 1;
var b = Math.floor(Math.random() * 20) + 1;
var question = document.getElementById("question");
question.textContent = a + " + " + b;
function answer() {
var result = a + b;
var userAnswer = document.getElementById("user-answer").value;
if (userAnswer == result) {
sound.play();
//clearTimeout();
play();
}
if (userAnswer != result) {
document.getElementById("user-answer").classList.add("wrong");
// document.getElementById("user-answer").disabled = true;
console.log(result);
console.log(userAnswer);
// setTimeout(remove, 1000);
}
}
window.addEventListener("keypress", function(event) {
if (event.key == "Enter") {
answer();
}
})
document.getElementById("send-answer").addEventListener("click", answer);
} else {
document.getElementById("win").textContent = "You won!";
}
}
Well, calling play repeatedly will create new local variables a and b and new instances of answer function closing over those a, b and will add new event listeners every time. You could:
move the declaration of answer outside play
share a,b across answer and play
add event listeners only once
Here is somewhat refactored version for a start:
(function () {
let count = 0;
let a = 0, b= 0;
function play() {
if (count < 10){
// setTimeout(loss, 30000);
count += 1;
document.getElementById("user-answer").value = "";
a = Math.floor(Math.random() * 20) + 1;
b = Math.floor(Math.random() * 20) + 1;
var question = document.getElementById("question");
question.textContent = a + " + " + b;
} else {
document.getElementById("win").textContent = "You won!";
}
}
function answer(){
var result = a + b;
var userAnswer = document.getElementById("user-answer").value;
if(userAnswer == result){
//sound.play();
//clearTimeout();
play();
}
if(userAnswer != result) {
document.getElementById("user-answer").classList.add("wrong");
// document.getElementById("user-answer").disabled = true;
console.log(result);
console.log(userAnswer);
// setTimeout(remove, 1000);
}
}
window.addEventListener("keypress", function(event){
if (event.key == "Enter"){
answer();
}
})
document.getElementById("send-answer").addEventListener("click", answer);
play();
})()
<div>
<div id="question">
</div>
<input type="text" id="user-answer"/>
<input type="button" id="send-answer" value="Send answer"></input>
<div id="win">
</div>
</div>
I have very little to no knowledge when it comes to using JavaScript. I have 24 of the same image given an id from q1 - q24. my code allows for the 24 images to be changed to image2 one at a time, but I need for it to stop and display a text/alert when image2 is clicked.
<script>
{
let num = 1;
function sequence()
{
let back = 1;
while (back < 25)
{
if(back == 1)
{
document.getElementById("q24").src = "question.jpg";
}
else
{
document.getElementById("q" + (back-1)).src = "question.jpg";
}
back++
}
document.getElementById("q" + num).src = "question2.png";
num = num + 1;
if(num > 24){num = 1;}
}
setInterval(sequence, 500);
}
</script>
Save the interval timer to a variable. Then add a click listener to all the images that stops the timer if the current image is the one showing question2.jpg.
{
let num = 1;
for (let i = 1; i <= 24; i++) {
document.getElementById(`q${i}`).addEventListener("click", function() {
if (i == num) {
clearInterval(interval);
}
});
}
let interval = setInterval(sequence, 500);
function sequence() {
for (let i = 1; i <= 24; i++) {
if (i == num) {
document.getElementById(`q${i}`).src = "question2.jpg";
} else {
document.getElementById(`q${i}`).src = "question.jpg";
}
num = num + 1;
if (num > 24) {
num = 1;
}
}
}
}
While I don't fully understand your use case, you could create a click event listener on the document and check the target's src in it.
document.addEventListener('click', function(e) {
if (e.target.src === 'question2.png') {
alert('Clicked question 2');
}
});
I'm making a shot clock for my school's basketball team. A shot clock is a timer that counts down from 24 seconds. I have the skeleton for the timer right now, but I need to have particular key bindings. The key bindings should allow me to rest, pause, and play the timer.
var count=24;
var counter=setInterval(timer, 1000);
function timer()
{
count=count-1;
if (count <= 0)
{
clearInterval(counter);
return;
}
document.getElementById("timer").innerHTML=count + " secs";
}
I'm not sure what you meant by "rest" the timer, I interpret this as "pause", so:
Space = Pause / Play.
R = Reset.
var
count=24,
counter = setInterval(timer, 1000),
running = true;
function timer() {
count -= 1;
if (count <= 0) {
clearInterval(counter);
}
document.getElementById("timer").innerHTML = count + " secs";
}
window.addEventListener("keydown", function(e) {
switch(e.keyCode) {
case 32: // PLAY
running ? clearInterval(counter) : counter = setInterval(timer, 1000);
running = !running;
break;
case 82: // RESET
clearInterval(counter);
document.getElementById("timer").innerHTML = 24 + " secs";
count = 24;
running = false;
}
});
<div id="timer">24 secs</div>
I am not able to comment yet, but I recommend checking out this post Binding arrow keys in JS/jQuery
The linked post explains how to bind arrow keys using js/jquery. Using http://keycode.info/ you can find out the keycodes of your desired keys and replace the current values then continue to build your code from there.
Here is my code sample: http://codepen.io/anon/pen/vLvWJM
$(document).ready(function() {
var $timer = $('#timer');
var $timerStatus = $('#timerStatus');
var timerValue = 24;
var intervalId = null;
var timerStatus = 'stopped';
if(!$timer.length) {
throw 'This timer is missing a <div> element.';
}
$(document).keydown(function(k) {
if(k.which == 80) {
if(timerStatus === 'playing') {
clearInterval(intervalId);
timerStatus = 'stopped';
updateTimerStatus();
return;
}
intervalId = setInterval(function() {
playTimer();
timerStatus = 'playing';
updateTimerStatus();
}, 1000);
} else if(k.which == 82) {
clearInterval(intervalId);
resetTimer();
updateText();
timerStatus = 'stopped';
updateTimerStatus();
}
});
function playTimer() {
if(timerValue > 0) {
timerValue--;
updateText();
}
}
function resetTimer() {
timerValue = 24;
}
function updateText() {
$timer.html(timerValue);
}
function updateTimerStatus() {
$timerStatus.html(timerStatus);
}
});
<div id="timerStatus">stopped</div>
<div id="timer">24</div>
I am making a little game of Simon with jQuery. I have the functionality I want; start on page load, score, round numbers, etc, and the game works to an extent.
However, I still have a problem that I can't get my head around. I want to be able to prevent the user from being able to select the panels during the computer's turn. Currently, the user can trigger a sequence during the computer displaying its output, which causes havoc with buttons flashing and sounds going off.
The issue lies in setTimeout(). I tried to implement a variable 'cpuLoop' which turns to true when it's the computer's turn, and then back to false, but the implementation of setTimeout() means that there are still events on the event loop even after cpuLoop has been changed to false. The change to false changes immediately when of course it should wait until the setTimeout() has completed.
A similar problem is encountered when the reset button is clicked. When clicked, it should interrupt the setTimeout() events and restart the game. As it is, it continues outputting the computer's turn.
To get around this, I have attached the setTimeout() functions in the global scope and attempted to cut them off with clearInterval(var) but this seems to have no effect at the moment.
Here is my jQuery:
$(function(){
var counter = 0;
var cpuArray = [];
var cpuSlice = [];
var numArray = [];
var userArray = [];
var num = 1;
var wins = 0;
var losses = 0;
var cpuLoop = false;
// Initialise the game
function init(){
$('#roundNumber').html('1');
counter = 0;
cpuArray = [];
numArray = [];
userArray = [];
cpuLoop = false;
num = 1;
// Create cpuArray
function generateRandomNum(min, max){
return Math.floor(Math.random() * (max - min) + min);
}
for(var i = 1; i <= 20; i++){
numArray.push(generateRandomNum(0, 4));
}
for(var i = 0; i < numArray.length; i++){
switch(numArray[i]){
case 0:
cpuArray.push('a');
break;
case 1:
cpuArray.push('b');
break;
case 2:
cpuArray.push('c');
break;
case 3:
cpuArray.push('d');
break;
}
}
console.log('cpuArray: ' + cpuArray);
// Create a subset of the array for comparing the user's choices
cpuSlice = cpuArray.slice(0, num);
goUpToPoint(cpuSlice);
}
init();
var looperA, looperB, looperC, looperD;
// Cpu plays sounds and lights up depending on cpuArray
function cpuPlayList(input, time){
setTimeout(function(){
if(input === 'a'){
looperA = setTimeout(function(){
aSoundCpu.play();
$('#a').fadeOut(1).fadeIn(500);
}, time * 500);
} else if(input === 'b'){
looperB = setTimeout(function(){
bSoundCpu.play();
$('#b').fadeOut(1).fadeIn(500);
}, time * 500);
} else if(input === 'c'){
looperC = setTimeout(function(){
cSoundCpu.play();
$('#c').fadeOut(1).fadeIn(500);
}, time * 500);
} else if(input === 'd'){
looperD = setTimeout(function(){
dSoundCpu.play();
$('#d').fadeOut(1).fadeIn(500);
}, time * 500);
}
}, 1750);
};
// CPU takes its turn
function goUpToPoint(arr){
cpuLoop = true;
console.log('cpuLoop: ' + cpuLoop);
for(var i = 0; i < arr.length; i++){
cpuPlayList(arr[i], i);
}
cpuLoop = false;
console.log('cpuLoop: ' + cpuLoop);
}
// User presses restart button
$('.btn-warning').click(function(){
clearTimeout(looperA);
clearTimeout(looperB);
clearTimeout(looperC);
clearTimeout(looperD);
init();
});
// Array comparison helper
Array.prototype.equals = function (array) {
// if the other array is a falsy value, return
if (!array)
return false;
// compare lengths - can save a lot of time
if (this.length != array.length)
return false;
for (var i = 0, l=this.length; i < l; i++) {
// Check if we have nested arrays
if (this[i] instanceof Array && array[i] instanceof Array) {
// recurse into the nested arrays
if (!this[i].equals(array[i]))
return false;
}
else if (this[i] != array[i]) {
// Warning - two different object instances will never be equal: {x:20} != {x:20}
return false;
}
}
return true;
}
// User presses one of the four main buttons
function buttonPress(val){
console.log('strict?: ' + $('#strict').prop('checked'));
console.log('cpuSlice: ' + cpuSlice);
userArray.push(val);
console.log('userArray: ' + userArray);
if(val === 'a'){ aSoundCpu.play(); }
if(val === 'b'){ bSoundCpu.play(); }
if(val === 'c'){ cSoundCpu.play(); }
if(val === 'd'){ dSoundCpu.play(); }
// If the user selected an incorrect option
if(val !== cpuSlice[counter])
//Strict mode off
if(!$('#strict').prop('checked')){
// Strict mode off
alert('WRONG! I\'ll show you again...');
userArray = [];
console.log('cpuSlice: ' + cpuSlice);
goUpToPoint(cpuSlice);
counter = 0;
} else {
//Strict mode on
losses++;
$('#lossCount').html(losses);
ui_alert('You lose! New Game?');
return;
} else {
// User guessed correctly
counter++;
}
if(counter === cpuSlice.length){
$('#roundNumber').html(counter + 1);
}
if(counter === 5){
ui_alert('YOU WIN!');
$('#winCount').html(++wins);
return;
}
console.log('counter: ' + counter);
if(counter === cpuSlice.length){
console.log('num: ' + num);
cpuSlice = cpuArray.slice(0, ++num);
console.log('userArray:' + userArray);
userArray = [];
console.log('cpuSlice: ' + cpuSlice);
goUpToPoint(cpuSlice);
counter = 0;
}
}
// Button presses
$('#a').mousedown(function(){
if(!cpuLoop){
buttonPress('a');
}
});
$('#b').mousedown(function(){
if(!cpuLoop) {
buttonPress('b');
}
});
$('#c').mousedown(function(){
if(!cpuLoop){
buttonPress('c');
}
});
$('#d').mousedown(function(){
if(!cpuLoop){
buttonPress('d');
}
});
// jQuery-UI alert for when the user has either won or lost
function ui_alert(output_msg) {
$("<div></div>").html(output_msg).dialog({
height: 150,
width: 240,
resizable: false,
modal: true,
position: { my: "top", at: "center", of: window },
buttons: [
{
text: "Ok",
click: function () {
$(this).dialog("close");
init();
}
}
]
});
}
// Sound links
var aSoundCpu = new Howl({
urls: ['https://s3.amazonaws.com/freecodecamp/simonSound1.mp3'],
loop: false
});
var bSoundCpu = new Howl({
urls: ['https://s3.amazonaws.com/freecodecamp/simonSound2.mp3'],
loop: false
});
var cSoundCpu = new Howl({
urls: ['https://s3.amazonaws.com/freecodecamp/simonSound3.mp3'],
loop: false
});
var dSoundCpu = new Howl({
urls: ['https://s3.amazonaws.com/freecodecamp/simonSound4.mp3'],
loop: false
});
});
and here is a link to the app on codepen. Many thanks
This seemed to work OK for me for disabling user input during the computer's turn:
function goUpToPoint(arr){
cpuLoop = true;
console.log('cpuLoop: ' + cpuLoop);
for(var i = 0; i < arr.length; i++){
cpuPlayList(arr[i], i);
}
//cpuLoop = false;
setTimeout(function() {
cpuLoop = false;
}, arr.length * 500 + 1750);
console.log('cpuLoop: ' + cpuLoop);
}
Then for the reset button, put this with your globals above function init()
timeoutsArray = [];
and make these function edits:
// Cpu plays sounds and lights up depending on cpuArray
function cpuPlayList(input, time){
timeoutsArray.push(setTimeout(function(){
if(input === 'a'){
timeoutsArray.push(setTimeout(function(){
aSoundCpu.play();
$('#a').fadeOut(1).fadeIn(500);
}, time * 500));
} else if(input === 'b'){
timeoutsArray.push(setTimeout(function(){
bSoundCpu.play();
$('#b').fadeOut(1).fadeIn(500);
}, time * 500));
} else if(input === 'c'){
timeoutsArray.push(setTimeout(function(){
cSoundCpu.play();
$('#c').fadeOut(1).fadeIn(500);
}, time * 500));
} else if(input === 'd'){
timeoutsArray.push(setTimeout(function(){
dSoundCpu.play();
$('#d').fadeOut(1).fadeIn(500);
}, time * 500));
}
}, 1750));
};
// User presses restart button
$('.btn-warning').click(function(){
for(var i = 0; i < timeoutsArray.length; i++) {
clearTimeout(timeoutsArray[i]);
}
timeoutsArray = [];
init();
});
I think you were replacing some of your looperX variable values. Using an array to store all of your setTimeout functions guarantees that they all get cleared.
Your problem is that setTimeout is an asynchronous function, which means that once you called it, the code after it continue as if it is done.
If you want the code to wait until the end of your loop, you need to invoke it at the end of the setTimeout function.
You could split your function in two (in your case it's the goUpToPoint function), something like this:
function first_part() {
//Call setTimeout
setTimeout(function() { some_function(); }, time);
}
function second_part() {
// Rest of code...
}
function some_function() {
//Delayed code...
...
second_part();
}
Since you are calling your function a number of times, I would create a global counter that you can decrease at the end of each setTimeout call, and call the second_part function only if the counter is 0:
var global_counter = 0;
function first(num) {
//Call setTimeout
global_counter = num;
for (var i = 0; i < num; i++) {
setTimeout(function() { some_function(); }, time);
}
}
function second() {
// Rest of code...
}
function some_function() {
//Delayed code...
...
// Decrease counter
global_counter--;
if (global_counter == 0) {
second();
}
}
function animate(elem,style,from,to,time) {
if (!elem) return;
var start = new Date().getTime(),
timer = setInterval(function() {
var step = Math.min(1, (new Date().getTime() - start) / time);
elem.style[style] = (from + step * (to - from))+'px';
if (step == 1) clearInterval(timer);
}, 25);
elem.style[style] = from + 'px';
}
Here is my code. and i need to pause and play in it
Finally i found the answer
function animate(elem,style,to,time,callback)
{
if(!elem) return;
_animating = true;
curAnimElem = elem; /*stores the current Element*/
curStyle = style; /*stores the current style*/
curTo = to; /*stores the current to 'px'*/
curCallback = callback; /*stores the current callbak function*/
if(style === 'left'){ from = elem.offsetLeft; }
else if(style === 'top'){ from = elem.offsetTop; }
var start = new Date().getTime(),
animTimer = setInterval(function()
{
if(!pauseV)
{
pauseTime = Math.round(time - (new Date().getTime()-start));
var step = Math.min(1,(new Date().getTime()-start)/time);
elem.style[style] = (from+step*(to-from))+'px';
if( step == 1 ){ _animating = false; clearInterval(animTimer); if(callback){callback(); callback = null;} }
}
else{clearInterval(animTimer);}
},25);
elem.style[style] = from + 'px';
}
the above code is to animate the elements(left or top only). to PAUSE and PLAY, include the below code in pause/play event function.
function pauseFun()
{
if(pauseV)
{
pauseV = false;
if(_animating){ animate(curAnimElem,curStyle,curTo,pauseTime,curCallback); }
}
else if(!pauseV)
{pauseV = true;}
}
Its works for me......
Thanks.
Use clearInterval(timer) to pause the animation and then timer = setInterval(...) again to resume it. You would need to break the setInterval callback out into its own named function (inside the body of the first function) to make resuming easier.
try this:
var doAnimation=true;
var timer=0;
var start=undefined;
function animate(elem,style,from,to,time)
{
if(!elem && doAnimation) return;
if(start==undefined)start = new Date().getTime();
timer = setInterval(function(){
var step = Math.min(1,(new Date().getTime()-start)/time);
elem.style[style] = (from+step*(to-from))+'px';
if( step == 1){
doAnimation=false;
clearInterval(timer);
elem.style[style] = from+'px';
start=undefined;
}},25);
}
function fun(){
doAnimation=!doAnimation;
if(doAnimation){play();}
else{pause();}
}
function play(){
animate( document.getElementById('box'),'left',0 , 200, 7000);
}
function pause(){
clearInterval(timer);
}
$(document).ready(function(){
play();
});
Check out the fiddle here:
http://jsfiddle.net/sunnykumar08/V9MHN/1/