setTimeout increases everytime it is cleared and called again - javascript

This is one of the stranger things I have come across.
http://cabbibo.com
Basically, on my site, there are a bunch of spinning shapes. Each spinning shape sets a timeout when it is first called, to make it spin. Whenever a visitor clicks on the webpage, these shapes are cleared and randomly generated again.
The problem is that on the second click they are spinning a bit faster. on the third click even faster, and by the fourth click they are spinning so fast that they get super choppy and unfluid.
When watching the site with the chrome://flags FPS counter, it will start at an EVEN 60 fps, and then by the time it gets to the fourth or fifth click, it will be jumping between 20 and 50 fps.
an abbreviated section of the code is as follows:
//creates timeout variable OUTSIDE the timeout function, so it can be cleared
var t;
var speedRandom;
function getRandSpeed(){
var randomSpeed = (Math.random()*.01);
if (randomSpeed<=.001){
randomSpeed=.001;
}else if(randomSpeed>=.005){
randomSpeed=.005;
}
console.log(randomSpeed);
if (rightSpin==0){
speedRandom=randomSpeed;
rightSpin=1;
}else{
speedRandom=-randomSpeed;
rightSpin=0;
}
}
objs[whichImg].speed = speedRandom;
function rotateDrawing(whichImg){
//This is the function that centers the object
centerImg(whichImg);
//translates the object to the centered point (different for each frame)
objs[whichImg].ctx.translate(objs[whichImg].centeredX,objs[whichImg].centeredY);
//rotates to the correct angle
objs[whichImg].ctx.rotate(objs[whichImg].angle);
//draws the image
objs[whichImg].ctx.drawImage(objs[whichImg].image,0,0,objs[whichImg].height,objs[whichImg].width);
//adds to the angle of the object
objs[whichImg].angle+=objs[whichImg].speed;
t=setTimeout(function(){rotateDrawing(whichImg)},40);
}
//THE ABOVE CODE WILL BE EXECUTED FOR EVERY SHAPE (TOTAL AROUND 5)
//this is what is called when the screen is clicked
function destroy(){
functionThatClearsAllTheImages();
clearTimeout(t);
rotateDrawing(whichImg);
}
This code may have some holes in it, but it does function, the problem is that after the fifth click it is choppy.
I can add more code if anybody needs it, but any suggestions would be extraordinarily helpful!

The Problem was that each time I created a new object, the timeout lay within that creation code. This meant that the timeout was called 5 times, and when I cleared it, it was only cleared once.
In order to solve the problem, I created one timeout function that contained a loop within it that would rotate the shapes. This meant that everytime I had to clear it, it would essential clear the loop for all 5 shapes!

Related

getBoundingClientRect during throttled scroll event handling

I faced an issue during my project developing, it's related to a difference between getBoundingClientRect values with and without preventive break points during debugging. Trying to minimize repro, I got following.
const scrollHandler = () => {
setTimeout(() => {
const top = document.getElementById('test').getBoundingClientRect().top;
console.log(top);
});
};
document.getElementById('viewport').addEventListener('scroll', scrollHandler);
where the viewport is just scrollable div with fixed height. The content of the viewport is big enough to be able to fire at least one scroll event. I also created Plunker demo. All magic happens inside of setTimeout callback.
Now the steps. Success scenario.
Set break point at the beginning of the setTimeout callback.
Fire scroll event.
Make "step over" to obtain top value.
Execute document.getElementById('test').getBoundingClientRect().top in the console.
The results of 3 and 4 are the same.
Failed scenario.
Set break point at the end of the setTimeout callback.
Fire scroll event.
Get the value of top variable (no action is needed).
Execute document.getElementById('test').getBoundingClientRect().top in the console.
The results of 3 and 4 are not the same.
To avoid any misunderstanding with this repro, I recorded a short demo movie with the above steps.
In my project I'm doing calculations in a similar environment (throttled scroll events) and getting wrong results that don't match expectations. But when I debug it, I'm getting different picture; a preventive break point fixes calculations.
Is it a bug or known issue? Or did I miss something? should I refuse to use getBoundingClientRect in such a situation?
I'm not sure what it is that you are looking for but assume your animation on scroll isn't working as expected.
There is a tip on throttling scroll here that is incorrect. Let's say you throttle to every 30 milliseconds. If page stops scrolling 25 milliseconds after last animation then you're stuck with a scroll position 25 milliseconds before it stopped scrolling.
Maybe a better way to do it is this (can test in the console on this page):
var ol = document.querySelectorAll("ol")[2];
window.onscroll = ((lastExecuted)=>{
const expensiveHandler = e => {
lastExecuted=Date.now();
//do some animation maybe with requestAnimationFrame
// it may prevent overloading but could make animation
// glitchy if system is busy
console.log("1:",ol.getBoundingClientRect().top);
};
var timerID
return e=>{
if(Date.now()-lastExecuted>19){
//only animate once every 20 milliseconds
clearTimeout(timerID);
expensiveHandler(e);
}else{
//make sure the last scroll event does the right animation
// delay of 20 milliseconds
console.log("nope")
clearTimeout(timerID);//only the last is needed
timerID=setTimeout(e=>expensiveHandler(e),20);
}
}
})(Date.now())

ReactDOM.render has no effect in specific circumstances?

I have a method that renders an icon (font-awesome icon) inside provided container. Icon rendering can happen by user clicking on specific div or by program itself automatically calling rendering function when it mimics user clicking specific div.
To better understand this. I have created this so called TicTacToe game. It is possible to play player vs player or player vs cpu. If two players are playing then there is no problem, everything is rendered fine.
Now when player plays against CPU, something strange happens. If first player is CPU, then its first move is not rendered (icon does not appear). But the rest appears fine.
And if I make both players to be CPU (CPU plays against another CPU), then nothing gets rendered. Could it be that ReactDOM.render ignores changes if they happen too fast?;)
So here is part of the program (game):
$( document ).ready(function() {
const ICONS_CLASSES = {
x: 'fa fa-times fa-lg',
o: 'fa fa-circle-o fa-5x'
}
function renderMove(el, turn){
const classes = ICONS_CLASSES[turn];
ReactDOM.render(
React.createElement('span', {'className': classes}),
el
)
}
...
...
});
Also here is a function that is called by program itself to mimic clicking on a specific div in a game (well let say CPU "clicks" it):
function clickPosition(el, tic){
if(tic.state == 'running'){
// Save which player played, because after playing move, it will
// switch turn for another player.
const type = tic.turn.type;
const res = tic.play(el.id);
if(res != 'invalid')
renderMove(el, type);
updateInfo(tic.info)
// Add start block to be able to play again.
if(tic.state == 'stopped'){
// Highlight win combo if there is any
if(tic.winCombo)
highlightWin(tic.winCombo)
toggleEl($('#start'))
}
}
}
So calling such render as previously said will not render all the time (when playing with CPU).
But if I change that render to this:
el.innerHTML = `<span class="${classes}"></span>`;
Then it renders any icon just fine. No matter if game is played player vs player, cpu vs player, player vs cpu or even cpu vs cpu (in this case all icons are rendered instantly, because the end result is always draw).
Is there some gotcha with ReactDOM.render?
P.S. If you are interested, you can find full code here (currently it is enabled to be played cpu vs player, meaning first will play CPU. And because it is rendering with ReactDOM.render, first move will be invisible. Others should appear fine): https://codepen.io/andriusl/pen/qjyBdB
It looks like everything is OK with ReactDOM.render. But I was kind of right with guess about rendering happening too fast. But from another place.
There was a function (that I haven't mentioned in question) which cleared all rendered icons. And it would clear after starting the game. When players are playing, then there is no problem, because clearing does happen later then user clicks a div. But when cpu does that, it does much faster, so it plays its turn before clearing up is called and it clears that move icon.
So technically it is rendered, but it is instantly removed, looking like it was never rendered, at least to us humans:)
So fix was to move ReactDOM.unmountComponentAtNode before tic.start. Then it always clears before starting game, not after (or before starting another game).
$('#x, #o').on('click', function() {
const second = {'x': 'o', 'o': 'x'};
// Clear filled positions if any.
let positions = document.getElementsByClassName('pos');
_.each(positions, pos => {
ReactDOM.unmountComponentAtNode(pos);
})
tic.start(this.id, second[this.id]);
toggleEl($('#start'));
updateInfo(tic.info);
})

How to deal with over 100 moving rectangles without lag

I have this fiddle : https://jsfiddle.net/reko91/e6uwqnof/2/
On button press it creates 50 rectangles that all move down towards the bottom of the screen.
for(i=0;i<50;i++){
enemyArray.push(new enemy(normalBullet.x+i*5, normalBullet.y, normalBullet.speed, 1, 10, "#F00"));
}
Works fine on first click, but once I start adding more, it really starts to lag. Is there a best of practice way of dealing with hundreds of moving elements ? Or is HTML and Javascript not the best language to deal with this amount of moving data ?
Your main problem is in the update function:
function update() {
// enemy.update();
//if (keystate[SpaceBar]) {
$('#newEnemy').click(function() {
createNewEnemy()
})
//...
}
Probably a mistake, but you're attaching the event every time update gets called, which is 60 times per seconds! (Until it can't do it anymore, that is.)
This means that every time you press the button, you generate a ton of elements right in the canvas.
Move the event listener addition outsite update and you're golden.
You are assigning button pushes inside the frame loop, so when you push it, it's actually calling the button push however many times the loop has run.
Move this code outside:
$('#newEnemy').click(function() {
console.log("createEnemy");
createNewEnemy()
});

Javascript glitchy game

I have a game and when you hit the Obs's you get a game over screen. On it has a retry button and it resets it, it works most of the time but after the third or fourth retry it does not reset it. It just keeps it where it is. Why does it do that? Below is the code I used for the reset and a link. Thanks.
if (collides($(value), $('#player'))) {
$('#levelOne').stop();
$('#player').css('border', 'solid 1px yellow');
//GAME OVER SCREEN START
$('#GameOver').fadeIn();
$('#retry').click(function () {
$('#GameOver').fadeOut();
// NEW LOGIC
$("#levelOne").css('margin-top', '-1520px');
$("#player").css('border', 'solid 1px green')
$("#player").css('margin-left', '223px');
$('#levelComplete').hide();
$('#levelOne').animate({
'margin-top': '+=1520px'
}, speed);
handleCollisions()
});
}
http://jsfiddle.net/38bod36e/101/
You are constantly adding click event listeners in the handleCollisions function (this part: $('#retry').click(function () {), which is bound to kill your performance sooner or later because the number of events created/called will get insanely big. That is something you need to take care of to get rid of the glitches.
I moved the click handler function outside and it works just fine. I've also disabled the console log on every frame because it kills the performance as well.
See it here: http://jsfiddle.net/38bod36e/103/
Be careful with stuff like that if you want a consistent performance. See in the console how the number of events grows with every frame rendered in your original code: http://jsfiddle.net/38bod36e/105/

How can I reveal content and maintain its visibility when mousing to a child element?

I'm asking a question very similar to this one—dare I say identical?
An example is currently in the bottom navigation on this page.
I'm looking to display the name and link of the next and previous page when a user hovers over their respective icons. I'm pretty sure my solution will entail binding or timers, neither of which I'm seeming to understand very well at the moment.
Currently, I have:
$(document).ready(function() {
var dropdown = $('span.hide_me');
var navigator = $('a.paginate_link');
dropdown.hide();
$(navigator).hover(function(){
$(this).siblings(dropdown).fadeIn();
}, function(){
setTimeout(function(){
dropdown.fadeOut();
}, 3000);
});
});
with its respective HTML (some ExpressionEngine code included—apologies):
<p class="older_entry">Older<span class="hide_me">Older entry:
<br />
{title}</span></p>
{/exp:weblog:next_entry}
<p class="blog_home">Blog Main<span class="hide_me">Back to the blog</span></p>
{exp:weblog:prev_entry weblog="blog"}
<p class="newer_entry">Newer<span class="hide_me">Newer entry:
<br />
{title}</span></p>
This is behaving pretty strangely at the moment. Sometimes it waits three seconds, sometimes it waits one second, sometimes it doesn't fade out altogether.
Essentially, I'm looking to fade in 'span.hide_me' on hover of the icons ('a.paginate_link'), and I'd like it to remain visible when users mouse over the span.
Think anyone could help walk me through this process and understand exactly how the timers and clearing of the timers is working?
Thanks so much, Stack Overflow. You guys have been incredible as I walk down this road of learning to make the internet.
If you just want to get it working, you can try to use a tooltip plugin like this one.
If you want to understand how this should be done: first, get rid of the timeout, and make it work without it. The difference (from the user's point of view) is very small, and it simplifies stuff (developing and debugging). After you get it working like you want, put the timeout back in.
Now, the problem is you don't really want to hide the shown element on the navigator mouse-out event. You want to hide it in its own mouse out event. So I think you can just pass the first argument to the navigator hover function, and add another hover to dropdowns, that will have an empty function as a first argument, and the hiding code in its second argument.
EDIT (according to your response to stefpet's answer)
I understand that you DO want the dropdown to disappear if the mouse moves out of the navigator, UNLESS its moved to the dropdown itself. This complicates a little, but here is how it can be done: on both types of items mouse-out event, you set a timer that calls a function that hides the dropdown. lets say the timer is 1 second. on both kind of item mouse-in even, you clear this timer (see the w3school page on timing for syntax, etc). plus, in the navigator's mouse-in you have to show the dropdown.
Another issue with the timer in your code is that it will always execute when mouse-out. Due to the 3 seconds delay you might very well trigger it again when mouse-over but since the timer still exist it will fade out despite you actually have the mouse over the element.
Moving the mouse back and forth quickly will trigger multiple timers.
Try to get it to work without the timer first, then (if really needed) add the additional complexity with the delay (which you must keep track of and remove/reset depending on state).
Here was the final working code, for anyone who comes across this again. Feel free to let me know if I could have improved it in any ways:
$(document).ready(function() {
var dropdown = $('span.hide_me');
var navigator = $('a.paginate_link');
dropdown.hide();
$(navigator).hover(function(){
clearTimeout(emptyTimer);
$(this).siblings(dropdown).fadeIn();
}, function(){
emptyTimer = setTimeout(function(){
dropdown.fadeOut();
}, 500);
});
$(dropdown).hover(function(){
clearTimeout(emptyTimer);
}, function(){
emptyTimer = setTimeout(function(){
dropdown.fadeOut();
}, 500);
});
});

Categories