Turn a button into a loading circle animation - javascript

I would like to reproduce the animation below :
http://gph.is/1MILWlq
I found this but i would not have the progress bar (the loading time will not be defined) :
http://tympanus.net/Tutorials/CircularProgressButton/
How can I do it ?

You can add an event listener to temporarily replace it with an image and then have the callback of the ajax (or whatever happens) perform a replacement of it. In my example, I just used setTimeout to have it replace the button again after 3 seconds. Normally that functionality would go inside of the ajax callback (or whatever else it was waiting on):
Here's a jsFiddle example: https://jsfiddle.net/jetweedy/m0qpm1xh/2/
var myButton = document.getElementById('myButton');
var myParent = document.getElementById('myParent');
myButton.onclick = function() {
myParent.removeChild(myButton);
var myImage = document.createElement("img");
myImage.src = "http://chimplyimage.appspot.com/images/samples/classic-spinner/animatedCircle.gif";
myParent.appendChild(myImage);
setTimeout(function() {
myParent.removeChild(myImage);
myParent.appendChild(myButton);
}, 3000);
};
An alternative would be to write some JavaScript to gradually decrease/increase its size, position, color, and other properties while it processes whatever it's processing, and then (again, inside the callback) put a stop to all that (by ending a setInterval that you had initiated) after you're done.

Related

setInterval works then hangs browser javascript

I'm wanting to animate an element using setInterval. I've put my code into an object with 2 functions one to initialize and one to animate using setInterval. When I try to run the code the animation works once then causes the browser to hang. The only thing I can think of is an infinite loop being created somewhere however I can't see anywhere in my code that would cause this.
What is causing the browser to crash, how can this be overcome ?
<div id='box' style='position:absolute;height:100px;width:100px;background-color:#44e'>box</div>
<script>
var box = {
init : function(elemId) {
box.elem = document.getElementById(elemId);
box.timer = setInterval;
box.tick = 0;
box.animate();
},
animate: function() {
if(box.tick < 100) {
box.elem.style.top = box.tick +'px';
box.elem.style.left = box.tick +'px';
box.tick++;
} else {
clearInterval(box.timer);
}
var timer = setInterval(box.animate, 50)
}
}
box.init('box');
</script>
setInterval sets up a function that will be called repeatedly by the browser until you cancel the interval timer. Your code isn't doing that, because the only call to clearInterval is using box.timer, which is never set to a timer handle (the return value from setInterval). So you end up scheduling thousands of calls (a new series every time animate is called) and bringing the browser to its kneeds.
At the very least, this:
var timer = setInterval(box.animate, 50)
should probably be:
box.timer = setInterval(box.animate, 50);
Or you may want setTimeout (which schedules only one call back).

jQuery .load(function) synchronous

What is the best way to use the jQuery load function synchronously.
I need to load an image but can't execute the next line of code until that image has loaded.
I could loop a variable until the load has completed but was wondering if there was a better way of doing that.
var img = jQuery('<img src="' + url + '"/>').load(function () {
});
//Run code here once img load has comlpeted.
You can also use CallBack function to get Synchronous Behaviour -
var result = $('#main-container').load( 'html/Welcomeform.html',
function () {
if($("textarea").find("#mail-id")){
$("textarea#mail-id").val(email_id);
}
} );
From what I know, the load event will always fire asynchronously, except if the image is already cached (in some browsers). The only reliable solution is to put the code in a callback like you did. However, to make sure the load handler will always be fired in all browsers, even if the image is cached, make sure to add the handler before setting the src property of the image.
var img = jQuery('<img src="' + url + '"/>').load(runner);
function runner() {
//run code here once image is loaded
}
I arrived here looking for a similar solution. From the reads, it is not possible with .load, you need to use an AJAX request, as the question comment points out.
In my case I need to load a html file and I have added a transition to change the content. The old content were showed before the new one after the transition even if I was showing the content inside the load callback.
var main = $("#main");
main.load("about.html", displaySection);
function displaySection () {
main.show('blind');
}
My workaround has been to run the transition that shows the loaded content inside a timeout function with a of 200 for the delay parameter.
var main = $("#main");
main.load("about.html", displaySection);
function displaySection () {
setTimeout(function() {
main.show('blind');
}, 200);
}
The problem could be if the connection is so slow that the new page takes more than 200 ms to load, but in this case I wonder the callback will be launched later on. I don't understand why is not working without the timeout that I feel quite ugly, but it solved my problem ... just in case any other has not given a thought on this possibility.
The callback function in load() will fire once the basic elements of the screen have been retrieved, (the actual html) but doesn't wait for images to finish, you can use this to make it wait.
$('#holder').load(function() {
var imgcount = $('#holder img').length;
$('#holder img').load(function(){
imgcount--;
if (imgcount == 0) {
/* now they're all loaded, let's display them! */
}
});
});

Unable to get setTimeout to work in javascript

I am using jquery to slide up an down a div.
For some reason setTimeout is not working (looks like a function scope issue).
Not able to figure out what is wrong with the below code.
(both functions are inside $(document).ready(function(){ } )
$('.slider-thumb').click(function(){
var source = $(this).attr("src");
$('#image_view').css('background-image',"url("+source+")");
$('#image_view').slideDown(1000, calbck);
initiate_timeout();
function calbck(){}
});
function initiate_timeout(){
var time_out = setTimeout(function() {
$('#image_view').slideUp(1000, calbck);
},2000);
}
Indenting the source code shows that you indeed have some scoping issue. The calbck function is private to the click handler function, and is thus not visible to initiate_timeout function. Either make calbck a top-level function, or make initiate_timeout a function local to the click handler function.
$('.slider-thumb').click(function() {
var source = $(this).attr("src");
$('#image_view').css('background-image',"url("+source+")");
$('#image_view').slideDown(1000, calbck);
initiate_timeout();
function calbck(){}
});
function initiate_timeout(){
var time_out = setTimeout(function() {
$('#image_view').slideUp(1000, calbck);
}, 2000);
}
You have some JS errors and scoping issues. Why would you ever have debugging turned off when trying to troubleshoot an error? Change your code to this:
$('.slider-thumb').click(function(){
var source = $(this).attr("src");
$('#image_view').css('background-image',"url("+source+")");
$('#image_view').slideDown(1000);
setTimeout(function() {
$('#image_view').slideUp(1000);
},2000); // will start 2 seconds after slideDown starts (which is 1 second after it completes)
});
or even better, use the completion function of the first animation to set the timer:
$('.slider-thumb').click(function(){
var source = $(this).attr("src");
$('#image_view').css('background-image',"url("+source+")");
$('#image_view').slideDown(1000, setTimeout(function() {
$('#image_view').slideUp(1000);
},1000)); // will stay open for 1 second before sliding up again
});
Or, even better, using jQuery's delay/queuing, you can do this:
$('.slider-thumb').click(function(){
var source = $(this).attr("src");
$('#image_view').css('background-image',"url("+source+")");
$('#image_view').slideDown(1000).delay(1000).slideUp(1000); // delay 1 sec between effects
});
The calbck you were trying to pass to slideUp was not defined in the scope you were using it (inside of initiate_timout()). It was private to your click handler.
jQuery probably has better ways to chain effects than using your own timer, but I see no reason why this code shouldn't work if it matches your HTML.
Note: if your background image wasn't already pre-cached, it may not be loaded right away when your slideDown starts.
Are you getting any errors? You're passing an undefined value as calbck to slideUp in the setTimeout function.
It works for me (with my modified organization): http://jsfiddle.net/v3cjG/1/
And when I run your exact code ( http://jsfiddle.net/v3cjG/2/ ) I get an error for "Can't find variable calbck"--the var I said you were passing without defining.

image.Onload does not complete the image

I need to load an array of images in Javascript, but I want to make sure that all the images are loaded before starting drawing them. So, I busy-wait for every image onLoad event to be called. First I create images and set their source and onload function:
// Load images from names
for (i = 0; i < this.nImages; i++) {
this.imagesArray[i] = new Image();
this.imagesArray[i].onload = this.onLoad(i);
this.imagesArray[i].src = images[i];
}
This is the onLoad function, member of the class I'm developing (the first two steps were in the constructor):
MyClass.prototype.onLoad = function (nimage) {
console.log ("Image completed? ", nimage, " ", this.imagesArray[nimage].complete);
this.imagesLoaded++;
}
Then I busy wait for all the onLoad functions to increment the counter (again, in the constructor):
while (this.imagesLoaded < this.nImages) {
// This is busy wait, and I don't like it.
continue;
}
So far, so good. But when I try to draw it on an HTMl5 canvas with my drawClass:
MyClass.prototype.refresh = function () {
// Gets one of the images in the range
var imageNum = this.GetImageNum();
// Test for completeness. This gives FALSE :(
console.log ("completeness for image number ", imageNum, " is: ", this.imagesArray[imageNum].complete);
this.drawClass.draw(this.imagesArray[imageNum], this.xOrigin, this.yOrigin);
}
The console.log line gives false and I get the infamous NS_ERROR_NOT_AVAILABLE exception.
Please not that the refresh() function is called after the onLoad() function, according to Firebug.
What am I missing here?
You need to assign onload before setting the source, otherwise the loading may be completed before the script gets to set the handler. Maybe that already fixes it.
Re the busy waiting, that is indeed never a good thing. It's hard to suggest alternatives, as you are not showing why you need to wait in the first place. But what might be a good idea is extending the onload handler to detect whether the image array is complete, and if it is, to start the following action - that would make the busy waiting unnecessary.

How do I prevent the concurrent execution of a javascript function?

I am making a ticker similar to the "From the AP" one at The Huffington Post, using jQuery. The ticker rotates through a ul, either by user command (clicking an arrow) or by an auto-scroll.
Each list-item is display:none by default. It is revealed by the addition of a "showHeadline" class which is display:list-item. HTML for the UL Looks like this:
<ul class="news" id="news">
<li class="tickerTitle showHeadline">Test Entry</li>
<li class="tickerTitle">Test Entry2</li>
<li class="tickerTitle">Test Entry3</li>
</ul>
When the user clicks the right arrow, or the auto-scroll setTimeout goes off, it runs a tickForward() function:
function tickForward(){
var $active = $('#news li.showHeadline');
var $next = $active.next();
if($next.length==0) $next = $('#news li:first');
$active.stop(true, true);
$active.fadeOut('slow', function() {$active.removeClass('showHeadline');});
setTimeout(function(){$next.fadeIn('slow', function(){$next.addClass('showHeadline');})}, 1000);
if(isPaused == true){
}
else{
startScroll()
}
};
This is heavily inspired by Jon Raasch's A Simple jQuery Slideshow.
Basically, find what's visible, what should be visible next, make the visible thing fade and remove the class that marks it as visible, then fade in the next thing and add the class that makes it visible.
Now, everything is hunky-dory if the auto-scroll is running, kicking off tickForward() once every three seconds. But if the user clicks the arrow button repeatedly, it creates two negative conditions:
Rather than advance quickly through the list for just the number of clicks made, it continues scrolling at a faster-than-normal rate indefinitely.
It can produce a situation where two (or more) list items are given the .showHeadline class, so there's overlap on the list.
I can see these happening (especially #2) because the tickForward() function can run concurrently with itself, producing different sets of $active and $next.
So I think my question is:
What would be the best way to prevent concurrent execution of the tickForward() method?
Some things I have tried or considered:
Setting a Flag: When tickForward() runs, it sets an isRunning flag to true, and sets it back to false right before it ends. The logic for the event handler is set to only call tickForward() if isRunning is false. I tried a simple implementation of this, and isRunning never appeared to be changed.
The jQuery queue(): I think it would be useful to queue up the tickForward() commands, so if you clicked it five times quickly, it would still run as commanded but wouldn't run concurrently. However, in my cursory reading on the subject, it appears that a queue has to be attached to the object its queue applies to, and since my tickForward() method affects multiple lis, I don't know where I'd attach it.
You can't have concurrent executions of a function in javascript. You just have several calls waiting to execute in order on the pile of execution. So setting a flag when the function runs cannot work. When the event handler runs, it cannot run concurrently with a tickHandler() execution (javascript is threadless).
Now you have to define precisely what you want, because it doesn't appear in your question. What you happen when the user clicks, say, 3 times in rapid succession on the arrow? And how do the clicks interfere with the auto-scroll?
I'd say the easiest way would be to process a click only when the ticker is idle, so 3 clicks in a row will only tick once. And you make clicks replace auto-scroll and reset its timer. So I use a flag ticksInQueue that is raised when a tick is queue by a click and only lowered when the fadeIn has completed:
var ticksInQueue = 0,
timerId = setInterval(tickForward, 5000),
isPaused = false;
function tickForward() {
var $active = $('#news li.showHeadline');
var $next = $active.next();
if($next.length==0) $next = $('#news li:first');
$active.stop(true, true);
$active.fadeOut('slow', function() {
$active.removeClass('showHeadline');
});
setTimeout(function(){
$next.fadeIn('slow', function(){
$next.addClass('showHeadline');
if(ticksInQueue) ticksInQueue--; // only change
})}, 1000);
if(isPaused == true){
} else {
startScroll()
}
}
$('#arrow').click(function () {
if(ticksInQueue) return;
ticksInQueue++;
clearInterval(timerId);
timerId = setInterval(tickForward, 5000);
tickForward();
});
You can try a demo here : http://jsfiddle.net/mhaCF/
You can set up a bool variable and check it's value when the function executes. If it's already being run you can either wait for it to end or just skip it (don't know which is desirable)
var active = false;
function tickForward() {
if (!active) {
active = true;
// your function code goes here
// remember to change active to false once you're done
active = false;
} else {
// if you want to skip if active = true you don't need this else
// if you want to call your method later use something like this
setTimeout(tickForward, 1000);
}
}
This implementation using "new Date()" and "setTimeout" to avoid some race condition (concurrent execution) for some resource. That way each call to "NoConcurrExecFunc" is executed with at least 750 milliseconds between each one!
var last_call = new Date();
function NoConcurrExecFunc(param_a, param_b) {
var new_date = new Date();
var min_interval = 750;
if ((last_call - new_date) < 0) {
last_call = new Date(new_date.getTime() + min_interval);
// Do your stuff!
} else {
setTimeout(function () {
NoConcurrExecFunc(param_a, param_b);
}, min_interval);
}
}

Categories