javascript Date.now() continue to stack - javascript

I want to get the last picture from ipCamera, my function is like this.
basically, after one image load. then immediately startover loop(). I prefer not to use AJAX.
var toggle= function(_imageUrl ) { //refresh include and adjust
var img = document.getElementById('image');
var updater = function(){
var time = Date.now();
var loop = function() {
ImageURL = _imageUrl + '&' + time;
console.log(time)
img.setAttribute("src", imageURL);
ImageURL = ''; //set the image.src to empty
img.addEventListener('load', function() {
setTimeout(updater ,100)
}, false);
};
loop();
}
updater();
};
This function works fine, but apparently, the Date.now() continue to stack by each loading time. Here is the result of console.log(time);
First Loop:
1417935798237
Second loop:
1417935798237
1417935798925
ThirdLoop (as it took longer to load), thus the time stack more
1417935798925
1417935800057
1417935800057
1417935801226
1417935801227
1417935801228
1417935801228
The function has to evaluate more item by each loop, before rendering the last image, in the end it still deliver the last image, but by the 20th loop. the list is immense
Question:
1. what happened?
2. How to set Date.now() to only one last timestamp?

Everytime you call loop another eventListener is added because of the img.addEventListener call. Your eventListener will be called when the image's url changes ( causing the browser to reload the image ). That eventListener should only be added once. Move that outside of the loop function.
var toggle= function(_imageUrl ) { //refresh include and adjust
var img = document.getElementById('image');
img.addEventListener('load', function() {
setTimeout(loop ,100)
}, false);
var loop = function() {
var time = Date.now();
imageURL = _imageUrl + '&' + time;
console.log(time)
img.setAttribute("src", imageURL);
imageURL = ''; //set the image.src to empty
};
loop();
};

Because you are reusing the same img object in each loop, you keep adding more and more event listeners to it each time through the loop and thus when it loads, it doesn't just call your loop once, but N times.
Probably what you want to do is to move the adding of the event listener to outside the loop so it is only added once.
You will have to test this code carefully in whatever older browsers you want to support because a few years ago, I found some reliability issues with triggering load events more than one time on the same img object.
Also, you have references in your code to both _imageURL and imageURL. Are those supposed to be different?

Related

How can I change this into a loop instead of a recursive function?

So I have a piece of code like
var barlen = $('#SSWEprogressbar').width(),
$elems = $('[data-srcurl]'),
k = 0,
n = $elems.length;
LoadImage();
function LoadImage()
{
var $elem = $($elems[k]);
var img = new Image(),
url = $elem.attr('data-srcurl');
$(img).load(function(){
$('#SSWEloaderfront').attr('src',url);
$('#SSWEloadprogress').width((k+1)/n*barlen + "px");
var srctgt = $elem.attr('data-srctgt');
// change url to src attribute or background image of element
if ( srctgt == "srcattr" ){ $elem.attr('src',url); }
else if ( srctgt == "bgimg" ) { $elem.css('background-image',"url("+url+")"); }
// decide whether to exit the
if ( ++k == n ) { AllyticsSSWEPlayerShow(); }
else { LoadImage(); }
});
img.src = url;
}
and the reason I have it written that way is because load callback needs to be called before the stuff in the function can be executed again. If possible, I'd like to change this from a recursive function to a loop, but I don't know how to do that because there's no way to make a for or while loop "wait" before going on to the next iteration. Or is there?
As I mentioned in the comment you can easily resolve your problem, by using setTimeout(LoadImage, 100); in the else instead of calling the function directly. The 2nd parameter is the delay in ms.
If you understand why setTimeout(LoadImage, 0); is not stupid and not the same as calling the function directly then you understood setTimeout. It puts the function call in the queue, this means other events like clicks or keys that were pressed can be processed before the function is called again and the screen doesn't freeze. It's also impossible to reach max recursion like this, the depth is 1.

stopping dynamically generated setInterval

I am generating multiple charts each with their own setInterval to refresh the data. I have it set to clearInterval when the dynamically generated container is removed - but if I reload and it has the same id the old setInterval continues to run. Is there a way to set a dynamically named setInterval that can be stopped when the replacement is generated?
Right now I'm using:
function generateChart(data, location){
var chart = new Highcharts.Chart({
// blah blah blah
}, function(chart){
setInterval(function(){
if($('#'+location).length){
// I'm doing stuff every minute
}else{
clearInterval();
}
},60000);
});
}
What happens is, the location is a randomly generated string that becomes the element ID for the container for the Highchart and if they user saves the chart it becomes the unique identifier. If the user updates the chart that's saved and reloads the chart, the old one gets .removed() and the new one generated in its place. Now the new one has the same element ID as the old one and since the old interval finds the container it wants it attempts to continue updating - which is can't since its chart went poof.
is there a way to set a dynamic variable I can use for setInterval so that I can clearInterval on it?
var blob+location = setInterval(function(){ ...
and then
clearInterval(blob+location);
You can just use an object:
var myObj = {};
var location = "somevalue";
myObj[location] = setInterval(...
clearInterval(myObj[location]);
ok - since I couldn't seem to wrap my head around some of your answers I decided to go low tech.
function genToken(){
var num = Math.floor(Math.random() * 10000);
var token = 't-' + num;
return token;
}
function genLocation(){
var chartToken = genToken();
var newChart = '<div id="'+location+'" data-token="'+chartToken+'"></div>';
$('#chartHome').append(newChart);
}
// inside my chart function
var token = $('#'+location).data('token');
setInterval(function(){
if( $('[data-token="'+token+'"]').length ){
// still there - keep going
}else{
// all gone - time to stop
clearInterval();
}
},60000);
now when I do:
$('#'+location).remove();
the token also vanishes and won't be the same if I generate a new chart with the same location id.
Stop using setInterval, use setTimeout instead (How do I execute a piece of code no more than every X minutes?):
function generateChart(data, location) {
var element = $('#'+location);
var chart = new Highcharts.Chart({
// blah blah blah
}, foo);
var foo = function() {
if(element){
// I'm doing stuff every minute
setTimeout(foo, 6000);
}
};
}
To stop it, just avoid the setTimeout or make element = null.
Maybe my code is a little bit wrong (I'm getting sleep right now), but the thing is to use setTimeout and closures.
If inside foo, something longs more than 6 seconds you will be in troubles since setTimeinterval will call it again, please watch http://www.youtube.com/watch?feature=player_detailpage&v=i_qE1iAmjFg#t=462s , so, this way you ensure that this will run 6 seconds after the last completed stuff.
I'll let this example here to posterity:
http://jsfiddle.net/coma/vECyv/2/
var closure = function(id) {
var n = 0;
var go = true;
$('#' + id).one('click', function(event) {
go = false;
});
var foo = function() {
if(go) {
console.log(id, n++);
setTimeout(foo, 1000);
}
};
foo();
};
closure('a');
closure('b');
Not sure if anyone is still looking for this solution but I ran into this problem and chose the following approach.
For anyone dynamically creating private/anonymous intervals that need to be stopped based on some event. You can simply save the interval in a variable, then transfer that variable into a data property in your html element.
// Outer scope
let pos = 1
let interval = setInterval(() => {
if (pos < 700) {
pos++;
}
htmlEl.style.top = pos + "px";
});
htmlEl.setAttribute("data-interval", interval)
This will save the numeric identifier of your interval, providing that html element is somewhere in your DOM.
Then, later you can simply extract this data attribute and use it to cancel an interval.
let intervalId = document.querySelector("#someElement").dataset.interval;
clearInterval(intervalId);

Uncaught TypeError cannot set property 'src' of null

So on my new website in google chrome, I am trying to make the images switch every 5 seconds using a setInterval function. This seems to work, however I have the problem cannot set property src of null.
var images = new Array();
images[0] = "/images/grilled-chicken-large.png";
images[1] = "/images/sizzly-steak.png";
var img = document.getElementById("img");
function changeImage() {
for (var i = 0; i < 3; i++) {
img.src = images[i];
if (i == 2){i = i - 2;};
}
}
window.onload=setInterval("changeImage()", 5000);
I know the problem is that I am trying to get the element with an id of img before the page is done loading, but I've already got a window.onload, so i don't know what to do. Also, if i make an init() function containing the setInterval and the img variable, the page freezes.
Any ideas?
The problem is that you access element 2 of an array with only two elements (0 and 1).
But there is also a logical error in your script: Where do you store the index of the currently visible image? You need a global variable which holds that index.
var currentIndex = 0;
function changeImage() {
// Switch currentIndex in a loop
currentIndex++;
if (currentIndex >= images.length)
currentIndex = 0;
var img = document.getElementById("img");
img.src = images[currentIndex];
}
document.onload = function() {
setInterval(changeImage, 5000);
}
I've moved the img variable into the function so that it is assigned after five seconds when the document has loaded.
Just to clear things up: JavaScript is an event-based programming language. That means that the slideshow-change code is executed every time the interval fires, does some work (switch to the next slide) and then is done, so that the browser can render the page. If you iterate through the slides in a loop, there is no way for the browser to show the new slide because the script is still executing between the slides. The solution is what I've posted: Define a global variable that replaces the loop variable, and increment it every time the next slide is to be shown.
Here:
window.onload = function () {
var images = [
'http://placekitten.com/100/200',
'http://placekitten.com/200/100',
];
var img = document.getElementById( 'img' );
setInterval(function () {
img.src = img.src === images[0] ? images[1] : images[0];
}, 1000 );
};
Live demo: http://jsfiddle.net/wkqpr/
If you have more than two images, you can do this:
window.onload = function () {
var images = [
'http://placekitten.com/100/200',
'http://placekitten.com/200/100',
'http://placekitten.com/200/150',
'http://placekitten.com/150/300'
];
images.current = 0;
var img = document.getElementById( 'img' );
setInterval(function () {
img.src = images[ images.current++ ];
if ( images.current === images.length ) images.current = 0;
}, 1000 );
};
Live demo: http://jsfiddle.net/wkqpr/1/
Your for loop looks odd. Try that loop:
function changeImage(){
if(!img.attributes["index"]){img.attributes["index"]=0;return;};
img.attributes["index"]++;
img.src=images[img.attributes["index"]];
}
window.onload=function(){
window.img=document.getElementById("img");
setInterval("changeImage()",5000)
}
Should work.
You'll notice that I store the image index in an attribute of the image, as opposed to
a global variable. I think the global is slightly faster, but storing it in the image
means you can easily expand this to multiple images if needed.
Also, the problem with your browser freezing was that the following code:
for(var i=0;i<3;i++){
//Some more stuff
if(i==2){i-=2};
}
This loop is infinite, because whenever i reaches 2, i==2 becomes true, which means that iteration will reset i to 0 and start it all over again. The only way to prevent that would be a break; somewhere, which I don't see anywhere.
Tip: It is generally a bad idea to tamper with the index variable in for loops.
function changeImage()
{
for (var i = 0; i < 2; i++)
{
img.src = images[i];
}
}
window.onload=function()
{
setInterval(changeImage,5000);
}
Your last line in the for loop is completely useless and just complicate things,
Also, Your way of setting a window onload event is not standard!
EDIT: the src property is null because , obviously, your array size is only 2!!

If I load multiple images with the for loop, do I only need one img.onload function?

So I am loading multiple images onto multiple canvases basically (one image per canvas/ctx - its a slideshow). I want to make sure that each image is loaded before it attempts to draw the image onto the canvas.
Here is the code...
In Example 1 i'm using 3 onload events (one for each image)
In Example 2 i'm using one onload event, but it is the last image that gets called in the for loop
Question: Can i use Example 2 and be confident to assume that if the last image is loaded, then the images before must be loaded as well?
Also---- I attempted this already, but can it all be done inside a for loop? I wasn't able to get it to work. See Example 3
Example 1 3 onload events
var img=[0,'slide1','slide2','slide3'];
for (var i=1;i<=3;i++) {
img[i] = new Image();
img[i].src = '/images/slide' + i + '.png'
}
img[1].onload = function() { // checks to make sure img1 is loaded
ctx[1].drawImage(img[1], 0, 0);
};
img[2].onload = function() { // checks to make sure img2 is loaded
ctx[2].drawImage(img[2], 0, 0);
};
img[3].onload = function() { // checks to make sure img3 is loaded
ctx[3].drawImage(img[3], 0, 0);
};
Example 2 only the last onload event
var img=[0,'slide1','slide2','slide3'];
for (var i=1;i<=3;i++) {
img[i] = new Image();
img[i].src = '/images/slide' + i + '.png'
}
img[3].onload = function() { // checks to make sure the last image is loaded
ctx[1].drawImage(img[1], 0, 0);
ctx[2].drawImage(img[2], 0, 0);
ctx[3].drawImage(img[3], 0, 0);
};
Example 3 one onload in the for loop to do all the onload events
var img=[0,'slide1','slide2','slide3'];
for (var i=1;i<=3;i++) {
img[i] = new Image();
img[i].src = '/images/slide' + i + '.png'
img[i].onload = function() { // checks to make sure all images are loaded
ctx[i].drawImage(img[i], 0, 0);
};
}
I assume I can't do example 3 because an image probably won't be loaded by the time the for loop runs the second time and rewrites the onload event for the next image thus erasing the onload event for the prior image. Correct?
You'll have scoping issues with the last for-loop because of closures. In order for something like this to work, you'll want to break out of the closure by encapsulating your functional assignment in it's own function. This article explains it well.
Ideally, your last for-loop would look something like this:
var images = [0,'slide1.png', 'slide2.png', 'slide3.png'];
// Assign onload handler to each image in array
for ( var i = 0; i <= images.length; i++ ){
var img = new Image();
img.onload = (function(value){
return function(){
ctx[value].drawImage(images[value], 0, 0);
}
})(i);
// IMPORTANT - Assign src last for IE
img.src = 'images/'+images[i];
}
Also, keep in mind that you'll need to assign the img.src attribute AFTER the onload handler has been defined for some flavors of IE. (this little tidbit cost me an hour of troubleshooting last week.)

javascript slideshow grow faster, why?

I have an ad slide-show on my homepage. It consists of 2 images.
I preload these 2 images, making new Image() and setting .src to them.
I have a function giveNextName that return name of the next image (that should be in src attribute of the img element) (I do this, because soon, slideshow will consist of more than 2 images)
So the main code looks like:
BillBoard = {};
BillBoard.folder = "/pictures/cards/";
BillBoard.ext = "png";
BillBoard.$ = $("#billboard img");
BillBoard.pics = 2;
BillBoard.changeTime = 7000;
BillBoard.names = ["ad1","ad2"];
BillBoard.currentState = 0;
BillBoard.images = // array of preloaded Images
(function(){
var image, images = [];
for (var i=0; i<BillBoard.pics; i++) {
image = new Image ();
image.src = BillBoard.folder+BillBoard.names[i]+'.'+BillBoard.ext;
images.push (image);
}
return images;
}());
BillBoard.giveNextName = function(){/* getting the next name */};
// BillBoard change action
BillBoard.change = function(){
BillBoard.$.fadeOut(function(){
$(this).attr('src', BillBoard.giveNextName());
$(this).fadeIn();
});
}
// Starting BillBoard
window.setInterval(BillBoard.change, BillBoard.changeTime);
So, idea is simple. with window.setInterval I call BillBoard.change every n seconds. But, I don't know why, billboard becomes changing faster and faster, until there will be no picture at all (fadeIn doesn't have time to execute)
Where is my mistake?
UPD. Thanks to Yann Ramin for the link.
I shouldn't call BillBoard.change every n seconds via window.setInterval. Instead, I should add call of BillBoard.change in the callback of fadeOut().
I mean this code:
BillBoard.change = function(){
BillBoard.$.fadeOut(function(){
$(this).attr('src', BillBoard.giveNextName());
$(this).fadeIn();
window.setTimeout(BillBoard.change, BillBoard.changeTime);
});
}
// simply call the change function
// it will be calling itself every n seconds
BillBoard.start = (function(){
window.setTimeout(BillBoard.change, BillBoard.changeTime);
}());
See this for a likely culprit:
http://www.ozonesolutions.com/programming/2011/07/jquery-fadein-window-setinterval-a-bad-combination/

Categories

Resources