Optimal way of showing/hiding images in JavaScript - javascript

I read this question here "Is it faster to swap an img src or show/hide multiple images?"
The answer to the question has two options.
1) Alter the image source - Page loads faster, swapping consumes time.
2) Pre load images and then simply show/hide - Page loads a little slower, swapping is quicker
To my problem, I go with the second option as load time is 2nd priority for me. But is there best or most optimal way of writing the code?
Let's say I keep getting numbers (10,11,15,25,13,19 etc randomly) from somewhere. I have to show that many images (small dots, 30 in number, each image about 1kb).. I also have the condition, every dot represents 1.5. So I have written the following code.
var dots = new Array(30);
function onBodyLoad() {
for(var j=0;j<30;j++)
dots[j] = document.getElementById("dotimg"+j);
}
//called by some method every second.
//argument: the value received from somewhere.
function updateImages(s) {
var x = Math.round(s);
var dotPos = x/1.5;
for(var i=0;i<dotPos;i++) {
dots[i].style.visibility='visible'; //show that many dots
document.getElementById('dot-val').textContent=s;//value received shown in span
}
for(var j=dotPos;j<30;j++) dots[j].style.visibility='hidden';//hide remaining dots
}
So if the value received is 25 in one second, 17 dots will be shown.. if in the next second the value received is 15, 10 dots will be shown, the rest will be hidden. Is there a better way of writing the above code?

First of all, move the 'dot-val' setting out of the for loop (You're assigning the same value to it in each iteration of the for loop).
Also, you can change the display states in 1 loop, saving a for:
function updateImages(s) {
var x = Math.round(s);
var dotPos = x/1.5;
for(var i=0;i<30;i++) {
if(i < dotPos){
dots[i].style.display='inline-block'; // Assuming they're inline-block elements.
}else{
dots[i].style.display='none';
}
}
document.getElementById('dot-val').textContent=s;//value received shown in span
}
Now, if you really want to trim your code, skip the temp x var, and use a Ternary operator:
function updateImages(s) {
var dotPos = Math.round(s)/1.5;
for(var i=0;i<30;i++) {
dots[i].style.display = (i < dotPos)? 'inline-block' : 'none';
}
document.getElementById('dot-val').textContent = s;//value received shown in span
}

Related

How to perform a series of controls while a condition is not yet met

I'm looking for an efficient way to perform a series of controls (about 40) that should be interrupted as soon as one control identifies that a cell contains an inadequate value. This will be done in Google Sheets to identify what lines are ready to be exported.
I thought of using a loop but I don't think I can use a classic form of loops here since I don't repeat the same control over and over.
So here is how I could code it:
var rowsToExport = [];
var i = 0;
for (row = 0; row < 1000; row++){
if (cellA.value == X &&
cellB.value == Y &&
cellC.value == Z &&
... etc (40 times)
) {
rowsToExport[i] = row;
i++;
}
}
I assume that this isn't efficient. The reasoning is to save time and not go through the whole list of controls if a problem was identified in the earlier checks.
Thank you for your help :)

how to set anchor tag individual item to an array images

I added dynamic images display in specific div but I could not set link (a href)for each image individually.Could you help me?
Here is my script which I used but not working:
<script>
var i;
var timerid = 0;
var images = ["img1.jpg",
"img2.jpg",
"img3.jpg","img4.jpg"];
var countimages = 0;
function startTime()
{
if(timerid)
{
timerid = 0;
}
var tDate = new Date();
if(countimages == images.length)
{
countimages = 0;
}
if(tDate.getSeconds() % 4 == 0)
{
document.getElementById("img1").src = images[countimages];
}
countimages++;
timerid = setTimeout("startTime()", 800);
switch(countimages){
case images[0]: images[0].setAttribute('href',"dhow-cruise-creek-dubai.html");
images[0].innerHTML="dhow-cruise-creek-dubai.html";
document.appendChild(images[0]);
break;
case images[1]: images[1].setAttribute('href',"dhow-cruise-creek-dubai.html");
images[1].innerHTML="dhow-cruise-creek-dubai.html";
document.appendChild(images[1]);
break;
case images[2]: images[2].setAttribute('href',"dhow-cruise-creek-dubai.html");
images[2].innerHTML="dhow-cruise-creek-dubai.html";
document.appendChild(images[2]);
}
}
</script>
Several things here:
1: Your function is incrementing an array index (countimages) and wrapping it when it reaches the end of the index range of the array it is intended to subscript (images). You currently have two lines of code to accomplish this task, which are separated by another line of code. The two lines are
countimages++;
which is executed immediately after the subscripting of the aforementioned array, and
if (countimages == images.length) countimages = 0;
which is executed just before.
It would be much better, both for human readability and for code simplicity, to locate these two operations at the same spot in the code, because together they represent a single isolated and inseparable action. Also, the length cap can be applied more idiomatically and concisely using a modulus operation. The end result is you should delete the second line I showed above, and change the first to this:
countimages = (countimages+1)%images.length;
2: You are incrementing countimages in every evaluation of the function. This is happening even when the modulus test fails, and therefore the image is not changed. I suspect this is a mistake. Therefore I would change
if (tDate.getSeconds()%4 == 0) {
document.getElementById("img1").src = images[countimages];
}
countimages = (countimages+1)%images.length;
to
if (tDate.getSeconds()%4 == 0) {
document.getElementById("img1").src = images[countimages];
countimages = (countimages+1)%images.length;
}
3: I don't see any point in zeroing the timerid variable at the start of the function. It will inevitably be overwritten by the return value of the setTimeout() call later in the function. So the statement
if (timerid) timerid = 0;
should be removed.
4: The setTimeout() function supports two overloads. The first argument to the function can be either a function reference or a string of code. The former is preferable, both for performance and security reasons. So you should change
timerid = setTimeout('startTime()',800);
to
timerid = setTimeout(startTime,800);
But see below.
5: The setInterval() function is preferable to setTimeout() for a continuously repeating function call. Under this design, the function does not even need to reference timerid, or concern itself with its own invocation. We can just call setInterval() once during page load to start the chain of calls.
6: The switch statement at the end of the function is switching on countimages, which is of numeric type, against various elements of the images array specified by literal index, i.e. images[0], images[1], and images[2]. The images array holds string values representing image URLs, not numbers. So obviously this switch statement is incorrect. Also, the final element (images[3]) is omitted, which may be a mistake. If your intention was to switch on the indexes of the array, your case values should be 0, 1, etc. But see below.
7: Each of the case branches in the switch statement is identical to the others, except for the literal index. That is, they all follow this pattern, where i is the literal index:
case images[i]:
images[i].setAttribute('href','dhow-cruise-creek-dubai.html');
images[i].innerHTML = 'dhow-cruise-creek-dubai.html';
document.appendChild(images[i]);
break;
except that the final break; statement is missing from the final case branch.
This is an example of duplicate code that should be simplified by proper parameterization; in this case, parameterizing on i. Observe that the literal index always corresponds to the current value of countimages, so that is our i. In other words, the entire switch statement can be replaced with the following, again, assuming you wanted to switch on the indexes of the array:
images[countimages].setAttribute('href','dhow-cruise-creek-dubai.html');
images[countimages].innerHTML = 'dhow-cruise-creek-dubai.html';
document.appendChild(images[countimages]);
But see below.
8: The above lines of code are incorrect because they appear to be treating images as an array of elements, when it is in fact an array of strings. You cannot call setAttribute() on a string, nor is there a meaningful innerHTML property of strings, and you cannot append strings to the DOM tree using appendChild() (because strings do not implement the interface Node).
This brings us to your question. Your code seems to be trying to append a new anchor link element at the bottom of the entire document every time the image is advanced, but I doubt that's what you really want. I'm guessing you want to advance a single fixed anchor link element to a new href attribute and innerHTML content corresponding to the new image. To do this, I would recommend you change the array of strings to an array of hashes and store the href and innerHTML alongside the image URL using three key/value pairs.
9: The design of advancing the image and link during every multiple of 4 seconds, but checking for such a condition every 800 milliseconds, is very questionable. In some cases the check will be true twice in a multiple-of-4 second, in some cases it will be true only once during the multiple-of-4 second. And the moments the function is executed will drift, since the interval duration is not guaranteed to be exact. This would lead to some strange behavior. I suppose you may want this behavior, but I'm doubtful. Instead, I suspect what you're going for is for the image and link to advance once every 4 seconds. You can achieve this by removing the entire time test and just setting the interval to 4 seconds, that is, 4000 milliseconds.
Hence, we have:
var imageSpecs = [
{imageURL:'https://www.gravatar.com/avatar/8062178f34c7107a67ef00b681921287?s=328&d=identicon&r=PG&f=1',linkRef:'#1',innerHTML:'one' },
{imageURL:'https://www.gravatar.com/avatar/b57bf879dbb25c837c2e2ae730cab2cc?s=328&d=identicon&r=PG&f=1',linkRef:'#2',innerHTML:'two' },
{imageURL:'https://www.gravatar.com/avatar/166ed38dafa219c53980bb06cfce40b6?s=328&d=identicon&r=PG&f=1',linkRef:'#3',innerHTML:'three'},
{imageURL:'https://www.gravatar.com/avatar/0c8ea1549ebeff7bab9a282c4b965fa4?s=328&d=identicon&r=PG', linkRef:'#4',innerHTML:'four' },
];
// preload all images
for (let imageSpec in imageSpecs) {
let img = new Image();
img.src = imageSpec.imageURL;
} // end for
var nextImageSpecIndex = 0;
function advanceImageSpec() {
let imageSpec = imageSpecs[nextImageSpecIndex];
let imgElem = document.getElementById('img1');
imgElem.setAttribute('src',imageSpec.imageURL);
let linkElem = document.getElementById('link1');
linkElem.setAttribute('href',imageSpec.linkRef);
linkElem.innerHTML = imageSpec.innerHTML;
nextImageSpecIndex = (nextImageSpecIndex+1)%imageSpecs.length;
} // end advanceImageSpec()
var timerId = setInterval(advanceImageSpec,4000);
advanceImageSpec(); // immediate call to display first image immediately
#img1 { width:100px; height:100px; }
<div>
<img id="img1"/><br/>
<a id="link1"></a>
</div>
I'm not really sure what you're trying to do, but I assume you want the image to be a link that goes somewhere. Try putting a link tag around the img element with id img1 and giving that link tag the id `link1' like so:
<img id="img1"/>
Then change the script to this:
<script>
var i;
var timerid = 0;
var images = ["img1.jpg",
"img2.jpg",
"img3.jpg"];
var links = ["dhow-cruise-creek-dubai.html",
"dhow-cruise-creek-dubai.html",
"dhow-cruise-creek-dubai.html"];
var countimages = 0;
function startTime()
{
if(timerid)
{
timerid = 0;
}
var tDate = new Date();
if(countimages == images.length)
{
countimages = 0;
}
if(tDate.getSeconds() % 4 == 0)
{
document.getElementById("link1").href = links[countimages];
document.getElementById("img1").src = images[countimages];
}
countimages++;
timerid = setTimeout("startTime()", 800);
}
</script>
There's more that could be improved, but I'm sure you get where I'm going.

How to create and display result one at a time in an infinite loop in javascript?

I'm fairly new to HTML and Javascript. I want to know how to create an infinite loop from, let's say, myArray, list, or whatever and then display result one at a time. Can you please give me an example, hints, or anything with detailed explanation of how it works? I just want to understand on how things work. Thanks!
A very basic loop is a while loop:
while (condition) {
//code block to be executed
}
Typically you would use it like so:
var i = 0;
while (i < 10) {
//code block to be executed
i++;
//This block of code will continue until i >= 10
//adding 1 to the value of I each iteration
}
Easiest way to do a endless loop:
while (true) {
code block to be executed
}
//true will always be true so this will continue until it
//hits a return; statement, the end of time, or the software
//or hardware gives up
A common mistake that end up in an endless loop:
var i = 0;
while (i < 10) {
code block to be executed
//In this example i is never being increased so
//i will always be less than 10
}
A very practical way to do a while loop correctly:
var array = ['a','b','c'];
var i = 0;
while (i < array.length) {
alert(array[i]);
i++;
}
//This will alert a, alert b, then alert c
Another way to do the above is using a for loop:
var array = ['a','b','c'];
for (var i = 0; i < array.length; i++) {
alert(array[i];
}
//for loops are a good practice because you are less
//likely to leave out steps like defining the iterator,
//or increasing the iterator
OP
I'm trying to create something using HTML/Javascript that everytime I press a button called next item (I created one using <form></form>) it would display an item, or an image. The trick is I don't know how to keep displaying the item after the last position. After the last position it should go back to the first position. For example, for the array that you provide in your example you have [a, b, c], after I displayed c and press the button again I want to display a again and so forth. Can you give me hints or any other valuable info on how to do this?
Answer
JSFiddle Example:
http://jsfiddle.net/d2809p6z/
HTML
<button id="button">click me</div>
JS
var array = ['a','b','c'];
var index = 0;
document.getElementById('button').onclick=function(){
if (index >= array.length) {
index = 0;
}
alert(array[index]);
index++;
};

Generating random unique data takes too long and eats 100% CPU

WARNING: CPU Usage goes to 100%, be careful.
Link to the jsFiddle
This script has been written to design a dynamic snake and ladder board. Everytime the page is refreshed a new board is created. Most of the time all of the background images do not appear, and the CPU usage goes up to 100%. But on occasion all of them appear and the CPU usage is normal.
Opera shows some of the background images, Firefox lags and asks me if I wish to stop the script.
I believe that the problem is with these lines of code:
for(var key in origin) // Need to implement check to ensure that two keys do not have the same VALUES!
{
if(origin[key] == random_1 || origin[key] == random_2 || key == random_2) // End points cannot be the same AND starting and end points cannot be the same.
{
valFlag = 1;
}
console.log(key);
}
Your algorithm is very ineffective. When array is almost filled up, you literally do millions of useless iterations until you're in luck and RNG accidentally picks missing number. Rewrite it to:
Generate an array of all possible numbers - from 1 to 99.
When you need a random numbers, generate a random index in current bounds of this array, splice element and this random position, removing it from array and use its value as your desired random number.
If generated numbers don't fit some of your conditions (minDiff?) return them back to array. Do note, that you can still stall in loop forever if everything that is left in array is unable to fit your conditions.
Every value you pull from array in this way is guaranteed to be unique, since you originally filled it with unique numbers and remove them on use.
I've stripped drawing and placed generated numbers into array that you can check in console. Put your drawing back and it should work - numbers are generated instantly now:
var snakes = ['./Images/Snakes/snake1.png','./Images/Snakes/snake2.jpg','./Images/Snakes/snake3.gif','./Images/Snakes/snake4.gif','./Images/Snakes/snake5.gif','./Images/Snakes/snake6.jpg'];
var ladders = ['./Images/Ladders/ladder1.jpg','./Images/Ladders/ladder2.jpg','./Images/Ladders/ladder3.png','./Images/Ladders/ladder4.jpg','./Images/Ladders/ladder5.png'];
function drawTable()
{
// Now generating snakes.
generateRand(snakes,0);
generateRand(ladders,1);
}
var uniqNumbers = []
for(var idx = 1; idx < 100; idx++){ uniqNumbers.push(idx) }
var results = []
function generateRand(arr,flag)
{
var valFlag = 0;
var minDiff = 8; // Minimum difference between start of snake/ladder to its end.
var temp;
for(var i = 0; i< arr.length; ++i) {
var valid = false
// This is the single place it still can hang, through with current size of arrays it is highly unlikely
do {
var random_1 = uniqNumbers.splice(Math.random() * uniqNumbers.length, 1)[0]
var random_2 = uniqNumbers.splice(Math.random() * uniqNumbers.length, 1)[0]
if (Math.abs(random_1 - random_2) < minDiff) {
// return numbers
uniqNumbers.push(random_1)
uniqNumbers.push(random_2)
} else {
valid = true
}
} while (!valid);
if(flag == 0) // Snake
{
if(random_1 < random_2) // Swapping them if the first number is smaller than the second number.
{
var temp = random_1; random_1 = random_2; random_2 = temp
}
}
else // Ladders
{
if(random_1>random_2) // Swapping them if the first number is greater than the second number.
{
var temp = random_1; random_1 = random_2; random_2 = temp
}
}
// Just for debug - look results up on console
results.push([random_1, random_2])
}
}
drawTable()
I had a problem like this using "HighCharts", in a for loop - "browsers" have an in-built functionality to detect dead scripts or infinite loops. So the browsers halts or pop-ups up a message saying not responding. Not sure if you have that symptom!
This was resulted from a "loop" with a large pool of data. I wrote a tutorial on it on CodeProject, you might try it, and it might be your answer.
http://www.codeproject.com/Tips/406739/Preventing-Stop-running-this-script-in-Browsers

Inconsistent Javascript behaviour (IF statement nested in while loop)

I'm trying to write a simple Javascript(jQuery) function that randomly displays 6 Divs out of a possible 11. The code sort-of-works, it does randomly display around about half of the Divs, but it varaies between 4 and 8.
Can anyone tell me where I'm going wrong? It seems it should be so simple yet I'm completely lost!
My code:
<div class="offer">Offer 1</div>
<div class="offer">Offer 2</div>
... snip
<div class="offer">Offer 11</div>
<script src="query.min.js" type="text/javascript"></script>
<script>
var changed = 0;
while (changed < 6) {
$('.offer').each(function(index) {
if (changed < 6) {
var showOrNot = Math.floor(Math.random() * 2);
if (showOrNot == 1) {
$(this).addClass('offershow');
changed += 1;
$(this).text(changed); //debugging looking for the current value of changed
}
}
})
}
</script>
The problem as it stands now is that you have a bunch of unrelated attempts. If you have a bucket with 11 balls and have a 50% chance to remove each ball, you could end up with any number of balls between 0 and 11. Probability is skewed toward the center, but you don't get six and exactly six each time.
What you want is to remove six, and exactly six, balls, selected arbitrarily.
Try something more like this:
var offers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
for (var i = 0; i < 6; i += 1) {
// choose a remaining offer at random
var index = Math.floor(Math.random() * offers.length);
// retrieve the item being shown
var item = $('.offer').eq(offers[index]);
item.addClass('offerShow');
// remove this offer from the list of possibilities
offers.splice(index, 1);
}
EDIT: In the comments, the OP clarified that what he really wants is to take an arbitrarily-sized list of offers and show six of them. The code provided below addresses that need, rather than the strict request in the original question. I'm leaving the original code for reference.
var OFFERS_TO_SHOW = 6; // config, of sorts
// make sure no offers are shown now
$('.offer').removeClass('offerShow');
// show offers selected at random
for (var i = 0; i < OFFERS_TO_SHOW; i += 1) {
// get a list of offers not already being shown
var candidates = $('.offer').not('.offerShow');
// select one from this list at random
var index = Math.floor(Math.random() * offers.length);
// show this offer by adding the offerShow class
candidates.eq(index).addClass('.offerShow');
}
I think the problem is that you're not excluding divs that you've already set to show. So it's possible that your code to pick the next div to show is choosing one that's already been shown. If that makes sense? Trying changing your selector.....
$('.offer').not('.offershow').each(........
Keep in mind that addClass doesn't remove the existing classes, so your original selector will still prove true even though you've added the offershow class.
You're actually not checking wheter the selected div is already shown or not. Means when you're looping through all divs, there is always a possibility of setting a div visible twice. The script now thinks it was a new one and sets changed += 1;
Try adding something like this:
if(!$(this).hasClass('offershow')) {
[[rest of code here]]
}

Categories