Multitple tasks in a setTimeout not working as expected - javascript

I had asked a question earlier about a using setTimeout in a foreach using closures:
Javascript setTimeout in foreach: need help creating a closure
The selected answer works for me however I have a different problem now. Here is what I am trying to do:
playAllNotes(0);
function playAllNotes(index) {
if(notes.length > index) {
setTimeout(function() {
$('mydiv').addClass('playing-note');
playNote(notes[index]);
$('mydiv').removeClass('playing-note');
playAllNotes(index++);
}, 1000);
}
}
The above code (without the add and remove class) works as expected--play the note every second. However, what I wanted to do is to also change the color of a div when the note is playing, so I have the addclass before and removeclass after the playNote() method. The result is that the notes still play fine, but the css changing doesn't work--I think what's happening is that the add and remove are not going through the timeout logic, so it gets removed before I can notice it (because it actually does work in debugging mode).
I guess I don't completely understand how timeouts work, so I'd appreciate if anyone could help me fix the above code.
UPDATE:
playNote() uses the MIDI.js library to play a piano note:
function playNote(noteNumber){
var velocity = 127;
var delay = 0;
var instrumentChannel= 0;
MIDI.noteOn(instrumentChannel, noteNumber, velocity, delay);
}

Your problem is not related to timeout: it's related to the rendering engine behaviour.
HTML rendering is asynchronous: you add the playing-note class, but it won't be rendered immediately.
However, your code is synchronous: you immediately remove the playing-note class, and when the next render is applied... then nothing changed.
What you need is to add another timeout to delay the class removal, and give enought time for a human eye to notice the change.
For example:
playAllNotes(0);
function playAllNotes(index) {
if(notes.length > index) {
setTimeout(function() {
$('mydiv').addClass('playing-note');
playNote(notes[index]);
// let say 250ms is enought for people to notice the change.
setTimeout(function() {
$('mydiv').removeClass('playing-note');
}, 250);
playAllNotes(index++);
}, 1000);
}
}

You may also try this :
playAllNotes(0);
function playAllNotes(index) {
if(notes.length > index) {
setTimeout(function() {
$('mydiv').addClass('playing-note');
playNote(notes[index],index);
}, 1000);
}
}
function playNote(noteNumber,index){
var velocity = 127;
var delay = 0; var instrumentChannel= 0;
MIDI.noteOn(instrumentChannel, noteNumber, velocity, delay);
$('mydiv').removeClass('playing-note');
playAllNotes(index++);
}

Another way to do it
playAllNotes(0);
function playAllNotes(index) {
if(notes.length > index) {
setTimeout(function() {
$("mydiv").addClass("playing-note").delay(1000).queue(function(next){
$(this).removeClass("playing-note");
next();
});
playNote(notes[index]);
playAllNotes(index++);
}, 1000);
}
}

Related

The event .click fires multiple times

On the page there is a link with id get-more-posts, by clicking on which articles are loaded. Initially, it is outside the screen. The task is to scroll the screen to this link by clicking on it. The code below does what you need. But the event is called many times. Only need one click when I get to this element scrolling.
p.s. sorry for my bad english
$(window).on("scroll", function() {
if((($(window).scrollTop()+$(window).height())+250)>=$(document).height()){
$('#get-more-posts').click();
}
});
Try use removeEventListener or use variable with flag, just event scroll detached more at once
You can set up throttling by checking if you are already running the callback. One way is with a setTimeout function, like below:
var throttled = null;
$(window).on("scroll", function() {
if(!throttled){
throttled = setTimeout(function(){
if((($(window).scrollTop()+$(window).height())+250)>=$(document).height()){
$('#get-more-posts').click();
throttled = null;
}
}.bind(window), 50);
}
}.bind(window));
Here's an ES6 version that might resolve the scoping issues I mentioned:
let throttled = null;
$(window).on("scroll", () => {
if(!throttled){
throttled = setTimeout(() => {
if((($(window).scrollTop()+$(window).height())+250)>=$(document).height()){
$('#get-more-posts').click();
throttled = null;
}
}, 50);
}
});
The last argument of setTimeout is the delay before running. I chose 50 arbitrarily but you can experiment to see what works best.
I don't know how true it is, but it works. After the event (click), delete the element id, and then add it again, so the click is performed once. Scroll the page to the desired item, click again, delete the id and add it again. It works. Can someone come in handy.
window.addEventListener('scroll', throttle(callback, 50));
function throttle(fn, wait) {
var time = Date.now();
return function() {
if ((time + wait - Date.now()) < 0) {
fn();
time = Date.now();
}
}
}
function callback() {
var target = document.getElementById('get-more-posts');
if((($(window).scrollTop()+$(window).height())+650)>=$(document).height()){
$('#get-more-posts').click();
$("#get-more-posts").removeAttr("id");
//$(".get-more-posts").attr("id='get-more-posts'");
};
}
window.removeEventListener('scroll', throttle(callback, 50));

Can I detect if an arbitrary CSS transition has started

In my close function I want to do all my DOM clean-up stuff after css transitions have finished running. But there might not be any transitions running/might be multi-stage ones - (maintaining the stylesheets is out of my hands).
How would I go about writing a function something like the following
function close () {
myEl.removeClass('open');
if (animation is running/about to be run) {
// wait for transition to end, then recursively check to see if another
// one has started, wait for that ...
// then
cleanUpDOM();
} else {
cleanUpDOM();
}
}
My thoughts so far are to wrap the initial check in a timeout/requestAnimationFrame in order to give the animation a chance to start then checking to see if it's running. Unfortunately, without a transitionstart event I have no idea how to check if a transition has begun.
edit Answers recommending jquery are irrelevant as jquery animations are javascript animations, not CSS transitions
About transitionStart and transitionEnd events:
The transition can't starts from nowhere. Usually transition starts after some event, where you change the state of DOM element by changing styles by class or something else. So you know when transition starts because you start it in your code.
During the transition user I/O don't blocks, so transition is asynchronous and then transition will end you don't know right. So you needs transitionEnd event to do something then transition has finished in javascript.
About transitionEnd event:
Just look the jsfiddle
Here's my solution so far - a bit hacky and only works when which element might transition is known, and doesn't work with transition-property: all... but it's a promising start
function toCamelStyleProp (str) {
return str.replace(/(?:\-)([a-z])/gi, function ($0, $1) {
return $1.toUpperCase();
});
}
function toHyphenatedStyleProp (str) {
return str.replace(/([A-Z])/g, function (str,m1) {
return '-' + m1.toLowerCase();
}).replace(/^ms-/,'-ms-');
}
function getPrefixedStyleProp (prop) {
prop = toCamelStyleProp(prop);
prop = Modernizr.prefixed(prop);
return toHyphenatedStyleProp(prop);
}
function getStyleProperty (el, prop) {
return getComputedStyle(el,null).getPropertyValue(getPrefixedStyleProp(prop));
}
function doAfterTransition ($wrapper, cssClass, mode, $transitioningEl, callback) {
$transitioningEl = $transitioningEl || $wrapper;
var transitioningEl = $transitioningEl[0],
duration = +getStyleProperty(transitioningEl, 'transition-duration').replace(/[^\.\d]/g, ''),
transitioners = getStyleProperty(transitioningEl, 'transition-property').split(' '),
initialState = [],
changedState = [],
i,
callbackHasRun = false,
//makes sure callback doesn't get called twice by accident
singletonCallback = function () {
if (!callbackHasRun) {
callbackHasRun = true;
callback();
}
};
// if no transition defined just call the callback
if (duration === 0) {
$wrapper[mode + 'Class'](cssClass);
callback();
return;
}
for (i = transitioners.length - 1;i>=0;i--) {
initialState.unshift(getStyleProperty(transitioningEl, transitioners[i]));
}
$wrapper[mode + 'Class'](cssClass);
setTimeout(function () {
for (i = transitioners.length - 1;i>=0;i--) {
changedState.unshift(getStyleProperty(transitioningEl, transitioners[i]));
}
for (i = transitioners.length - 1;i>=0;i--) {
if (changedState[i] !== initialState[i]) {
$transitioningEl.transitionEnd(singletonCallback);
// failsafe in case the transitionEnd event doesn't fire
setTimeout(singletonCallback, duration * 1000);
return;
}
}
singletonCallback();
}, 20);
}
There is no way (that I know of) to detect if a transition is currently working in the background without knowing the element that is being transitioned.
However, if you can move away from transition to key frame animations, then you'd have the so needed event - animationStart and animationEnd and then it will be easy to figure out if there are running animations.
If you're planning to make css transition, you can check out jQuery Transit Plugin http://ricostacruz.com/jquery.transit/
Very powerfull and useful, you can get transform x value with. css('x') for example.
Have you tried the JQuery pseudo ":animated"?
if( $(elem).is(':animated') ) {...}
See More http://api.jquery.com/animated-selector/
Here is a function that waits for the page Html to become stable. i.e. when all animations are finished. In the example below it waits for the Html to be unchanging for 200 milliseconds and a maximum timeout of 2 seconds.
Call the function with ...
waitUntilHtmlStable(yourCallback, 200, 2000);
The function ...
waitUntilHtmlStable = function (callback, unchangedDuration, timeout, unchangedElapsed, html) {
var sleep = 50;
window.setTimeout(function () {
var newHtml = document.documentElement.innerHTML;
if (html != newHtml) unchangedElapsed = 0;
if (unchangedElapsed < unchangedDuration && timeout > 0)
waitUntilHtmlStable(callback, unchangedDuration, timeout - interval, unchangedElapsed + interval, newHtml);
else
callback();
}, sleep);
};
In my case I wanted to be sure new elements where present. If you want to track animation movement then change the document.documentElement.innerHTML to
JSON.stringify(Array.prototype.slice.call(document.documentElement.getElementsByTagName("*"), 0)
.map(function(e) {
var x = e;
var r = x.getBoundingClientRect();
while (r.width == 0 || r.height == 0) {
x = x.parentNode;
r = x.getBoundingClientRect();
}
return r;
}));
There is an unprefixed transitionstart event in IE10+. It is even cancelable.
https://msdn.microsoft.com/library/dn632683%28v=vs.85%29.aspx
On animation.css i found this.
You can also detect when an animation ends:
$('#yourElement').one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', doSomething);
read full doc here
you could use Jquery which would be much easier for example you could use .animate like this
(function(){
var box = $('div.box')
$('button').on('click', function(){
box.animate({ 'font-size' : '40px'})
.animate({'color': 'red'});
})
})();
or simply do a callback function

Execute after cleartimeout function finished (jQuery)

I have this istuation. I have a setTimeout to a function in which I fade out and fade in an element. In a few seconds this timeout is cleared with cleartimeout and right after is called .hide() to hide this element. The problem is that sometimes it doesnt hide the element. I have a feeling it has something to do with timing.
Example:
function first_func(){
$('.element').fadeOut(function(){
// Do other stuff like change element's position
$('.element').fadeIn();
});
interval1 = setTimeout(function(){first_func()},500);
}
function second_func(){
countdown--;
if (countdown<0){
last_func();
}
interval2 = setTimeout(function(){second_func()},1000);
}
function begin_func(){
first_func();
second_func();
}
function last_func(){
clearTimeout(interval1);
clearTimeout(interval2);
$('.element').hide();
}
So basically the problem is that in last_func I clear both intervals and HIDE the element, but sometimes the element is still visible on the page. So I am guessing that it does hide but the interval is still in progress and it fades back in.
If anyone would have some suggestion please
Just a suggestion, but this bit appears wrong to me:
function second_func(){
countdown--;
if (countdown<0){
end_func();
}
interval2 = setTimeout(function(){second_func()},1000);
}
Even if you're calling end_func() to stop everything, you're setting a new timeout after that.
function second_func(){
countdown--;
if (countdown<0){
end_func();
} else {
interval2 = setTimeout(second_func, 1000);
}
}
Another hint: To avoid that running fadeIn/fadeOuts affect the hiding of the element, you should clear the animation queue:
$('.element').stop(true, true).hide();
By default fadeIn and fadeOut use duration of 400 milliseconds, u can change it by set first parameter.
$('.element').fadeOut( [duration] [, callback] );
You seem to never call last_func, is end_func() supposed to be last_func()?
This works:
http://jsfiddle.net/CZ9hr/1/
May I suggest a simpler approach for what you seem to want to achieve: http://jsfiddle.net/fSEjR/2/
var countdown = 3,
$element = $('.element');
for (var i = 0; i < countdown; i++) {
$element.fadeOut().fadeIn(function() {
countdown--;
if (countdown === 0) $element.hide();
});
};
This works because animations are automatically queued in jquery.

Stopping increment at specific height

I am animating images within a logo in a slot-machine type of animation. I need it to stop animating once it gets to the top of the image (and send a callback if possible).
Currently, this is how I'm accomplishing the animation:
window.setInterval(function() {
$('#title-1 img').animate({bottom : '-=60px'})
}, 5000);
Any ideas on how I would get it to stop, and to send the callback?
So I assume you have a sprite image containing multiple logos, you want them to slide each 5 seconds until you reach the last one, and then call the callback?
var cnt = 6,
$img = $('#title-1 img'),
i = 0;
function animate_logo(cb) {
if (i < cnt) {
$('#title-1 img').animate({bottom : '-=60px'});
i += 1;
setTimeout(function () {animate_logo(cb)}, 5000);
}
else {
cb();
}
}();
var interval = window.setInterval(function() {
$('#title-1 img').animate({bottom : '-=60px'},
function(){
if(`some stop point`) clearInterval(interval);
}
);
}, 5000);
I would not suggest using a setInterval when dealing with animations due to the way newer browsers are making changes to the way setInterval and setTimeout work when the tab is not the active tab.
var $title1 = $("#title-1");
var $title1img = $title1.find('img');
function anim(){
if ($title1.height() < parseInt($title1img.css("bottom"))) {
setTimeout(function(){
$title1img.animate({bottom : '-=60px'},anim);
},5000);
}
}
$title1img.animate({bottom : '-=60px'},anim);
Edit: another reason not to use setInterval to fire off animations is due to the reqeustAnimationFrame that was implemented in 1.6 and removed in 1.6.3, which will more than likely be added back in 1.7. If you write code now that will be compatible later, that's less maintenance you will have to do later if you end up being required to upgrade.
Here's a jsfiddle http://jsfiddle.net/czUnU/
Edit: function...
function animColumn(title,img){
function anim(){
if (title.height() < parseInt(img.css("bottom")) {
setTimeout(function(){
img.animate({bottom : '-=60px'},anim);
},5000);
}
}
img.animate({bottom : '-=60px'},anim);
}
animColumn($("#title-1"),$("#title-1 img"));
animColumn($("#title-2"),$("#title-2 img"));
animColumn($("#title-3"),$("#title-3 img"));
http://jsfiddle.net/czUnU/1/

window.clearInterval is not working?

I'm using JS to animate two images by having them toggle on and off. I have an initial image which, when the animation is turned on, appears on the screen and stays on. The second image, which has a higher z value, is then set to toggle on and off every 1 second in the same location as the first image, so it appears as if the two are alternating.
I'm using window.setInterval to make the second image blink, but when I need to turn the animation off (and I'm removing both images from the screen), my window.clearInterval is not "working" The first image will be gone, but the second one keeps blinking on and off every second.
Code:
function notebookNotification(setting)
{
$("#lightNotificationContainer").show();
var notificationAnimation = window.setInterval('$("#darkNotificationContainer").toggle()', 1000);
if(setting == 0)
{
window.clearInterval(notificationAnimation);
$("#lightNotificationContainer").hide();
$("#darkNotificationContainer").hide();
}
}
Anyone see why it isn't working?
Reading between the lines, I think what you're saying is this:
You execute notebookNotification(1); and the animation starts
You execute notebookNotification(0); and the animation does not stop.
My guess is that you want notebookNotification(0) to disable the flashing.
In order to do that, you need to rework this function considerably. You need to store the intervalID that comes from setInterval in a variable that survives outside of the scope of this function and can be used for clearInterval on subsequent calls to this function.
For example:
var intervalID;
function notebookNotification(setting)
{
if(setting == 0)
{
if(intervalID) {
window.clearInterval(intervalID);
intervalID = null;
}
$("#lightNotificationContainer").hide();
$("#darkNotificationContainer").hide();
}
else
{
$("#lightNotificationContainer").show();
if(!intervalID) {
intervalID = window.setInterval('$("#darkNotificationContainer").toggle()', 1000);
}
}
}
Here, try this:
http://jsfiddle.net/WGxmy/
Saving the interval to a global variable -- not one inside a function -- lets you clear it later.
var keepflashing = true;
var isShowing = true;
function notebookNotification()
{
if(!isShowing)
$("#lightNotificationContainer").show();
else
$("#lightNotificationContainer").show();
isShowing = !isShowing;
if(keepflashing)
setTimeout( function(){ notebookNotification(setting); },100);
else
{
$("#lightNotificationContainer").hide();
$("#darkNotificationContainer").hide();
}
}
Maybe you can avoid calling clearInterval() generally?
function notebookNotification(setting)
{
if(setting == 0)
{
$("#lightNotificationContainer").hide();
$("#darkNotificationContainer").hide();
}
else
{
$("#lightNotificationContainer").show();
window.setInterval('$("#darkNotificationContainer").toggle()', 1000);
}
}

Categories

Resources