There is a code that I found in one of the answers on this website. I would like to know if that code can be written in simple JS or not? Also, if someone can help me understand it in terms of simple Javascript I would really appreciate your help.
The code is to fix the time lost during changing of tabs in chrome for timing functions like setTimeout and setInterval.
The code goes like this -
var div = $('div');
var a = 0;
var delay = (1000 / 30);
var now, before = new Date();
setInterval(function() {
now = new Date();
var elapsedTime = (now.getTime() - before.getTime());
if(elapsedTime > delay)
//Recover the motion lost while inactive.
a += Math.floor(elapsedTime/delay);
else
a++;
div.css("left", a);
before = new Date();
}, delay);
Please note that I am a newbie to this site and therefore with my current reputation I cannot comment on that answer of the question I am referring to.
There are two jquery calls in the OP's question:
$('div')
div.css("left", a)
The first:
$('div') -> [].slice.call(document.querySelectorAll('div'))
Return a collection of matched elements either found in the DOM based on passed argument(s) - jQuery docs
It returns a collection (array) of elements matched by the selector 'div'. That means all div elements on the page.
Lets strip down the plain javascript part:
document.querySelectorAll('div') select all elements on the document with selector 'div'. The problem here is that querySelectorAll returns a NodeList, not an array. It's an array-like object.
[].slice returns a shallow copy of a portion of an array - MDN
It can take an array like object (e.g. the result of querySelectorAll). The result is an actual array
At this point var div contains an array of all div elements on the page.
The second
div.css("left", a)-> div.forEach(function (item) {item.style.left = a + 'px';})
set one or more CSS properties for every matched element - jQuery Docs
It sets a style attribute to an jQuery element. In this case, it sets the css left property to a. a is a number, jQuery appends 'px' to that number to make it a string.
Lets strip down the plain javascript part:
div.forEach() Loop over the div variable, which is now an array. For every item in the array, execute the following function:
function (item) { item.style.left = a + 'px'; } This function takes a parameter (item). This is the a dom element, which is stored in the div array. Every dom element has a styles object with css properties. This function takes the a variable (a number) concats 'px' to it and sets that to the left property.
So basically, this function loops over every item in the div array and sets the styles.left property to the a value.
The complete code would be:
var div = [].slice.call(document.querySelectorAll('div'));
var a = 0;
var delay = (1000 / 30);
var now, before = new Date();
setInterval(function() {
now = new Date();
var elapsedTime = (now.getTime() - before.getTime());
if(elapsedTime > delay)
//Recover the motion lost while inactive.
a += Math.floor(elapsedTime/delay);
else
a++;
div.forEach(function (item) {
item.style.left = a + 'px';
})
before = new Date();
}, delay);
Related
I have this object resources:
var resources = { //Handles resources of all kinds.
number: 100,
money: 23000000,
science: 1000,
popularity: {
amount: 0,
upgProd: 0 //Amount produced from upgrades.
}
};
This looks like a normal object.
However, I'm trying to display a certain quantity popularity. Every time I try to display it, I get a NaN instead of a number.
I try to return console.log(resources.popularity.upgProd); but I still end up getting an undefined. I have no clue why since I define the variable but I still get undefined...
No errors in the IDE or in the console, just undefined when I console.log().
EDIT: Here is some surrounding context... this is my update function, that updates every 1/7 second. ALSO, the first value of resources.popularity.upgProd is 0, then the next become NaN.
function update() {
buffer++;
if (buffer == 35) {
checkVisibilityOnBuildings();
checkVisiblityOnUpgrades();
checkVisibilityOnResources();
buffer = 0;
} // Every 5 seconds (35 ticks) visibility will be checked and updated.
/* Number increasing. A bit tedious but no other way to do it yet. */
resources.number +=
((BUILDINGS[0].numProdBase * BUILDINGS[0].count) + //Now it's easy! Just do UPGRADES[n+1] for the next building.
(BUILDINGS[1].numProdBase * BUILDINGS[1].count) +
(BUILDINGS[2].numProdBase * BUILDINGS[2].count) +
(BUILDINGS[3].numProdBase * BUILDINGS[3].count));
//Science gained per tick. Var used to make the "scienceProductionTotalDisp" work properly
var scienceTotalPerTick =
(BUILDINGS[2].sciProdBase * BUILDINGS[2].count) +
(BUILDINGS[3].sciProdBase * BUILDINGS[3].count);
resources.science += scienceTotalPerTick;
//Display vars for html so that rounding errors don't happen.
var numDisp = Math.floor(resources.number);
var sciDisp = Math.floor(resources.science * 100) / 100;
var popDisp = Math.floor(resources.popularity.amount * 100) / 100;
console.log(Number(resources.popularity.upgProd));
var moneyTotalPerTick = Math.pow(resources.number, (1/(player.moneyRatio))) + 1; //Cash flow per 143ms (7n for total / sec ish)
var popularityTotalPerTick = (Number(resources.popularity.upgProd)) + 0;
resources.popularity += popularityTotalPerTick;
console.log(resources.popularity.upgProd);
resources.money += moneyTotalPerTick;
getId('moneyProductionTotalDisp').innerHTML = numFormat(Math.floor(moneyTotalPerTick * 7));
getId('moneyDisp').innerHTML = numFormat(Math.round(resources.money * 100) / 100);
getId('numberDisp').innerHTML = numFormat(numDisp);
getId('scienceDisp').innerHTML = numFormat(sciDisp);
getId('popularityDisp').innerHTML = numFormat(popDisp);
getId('scienceProductionTotalDisp').innerHTML =
numFormat(Math.floor(scienceTotalPerTick * 700) / 100);
getId('popularityProductionTotalDisp').innerHTML =
numFormat(Math.floor(popularityTotalPerTick * 700) / 100);
Thank you!
Here is your problem:
resources.popularity += popularityTotalPerTick;
popularity is an object, and that doesn't do what you want.
Since you overwrite it with the result of an object added by a value, you assign it s string [object Object]9 where the last digit is whatever was in popularityTotalPerTick.
You get NaN (Not a number) since you are using Number(x)in console.log(Number(resources.popularity.upgProd));. Why are you doing that?
Does getId do a lookup of the element in the dom every time your function is called? Have the object changed or are you querying the DOM for the same element 7 times per second?
Some thoughts about the other tings in your code:
resources.number +=
((BUILDINGS[0].numProdBase * BUILDINGS[0].count) +
(BUILDINGS[1].numProdBase * BUILDINGS[1].count) +
(BUILDINGS[2].numProdBase * BUILDINGS[2].count) +
(BUILDINGS[3].numProdBase * BUILDINGS[3].count));
I'm assuming that BUILDINGS is an array with all the buildings, and that you want to calculate the number of all buildings in the array. There is a function for that: reduce that takes two parameters: a function and the start value:
resources.number += // do you really want += here and not just = ?
BUILDINGS.reduce( (sum, item) => sum + (item.numProdBase * item.count), 0 );
If your aren't familiar with arrow-functions it could be replaced with:
function (sum, item) { return sum + (item.numProdBase * item.count) }
var scienceTotalPerTick =
(BUILDINGS[2].sciProdBase * BUILDINGS[2].count) +
(BUILDINGS[3].sciProdBase * BUILDINGS[3].count);
I'm not sure why you are only doing it for two buildings, and not for all, but you could use reduce here too, with slice
var scienceTotalPerTick =
BUILDINGS.slice(2,4).reduce( (sum, item) => sum + (item.sciProdBase * item.count), 0);
Notice that the parameters to slice is begin to end (end not included), therefor 2,4 gives you element 2 and 3.
With this part of the code...
getId('moneyProductionTotalDisp').innerHTML = numFormat(Math.floor(moneyTotalPerTick * 7));
getId('moneyDisp').innerHTML = numFormat(Math.round(resources.money * 100) / 100);
getId('numberDisp').innerHTML = numFormat(numDisp);
getId('scienceDisp').innerHTML = numFormat(sciDisp);
getId('popularityDisp').innerHTML = numFormat(popDisp);
I assume that getId is a function that fetches the element via document.getElementById or document.querySelector, and that you do this for every frame, and you get the same element every time. Now imagine that the element is a box, and that I demand that you get it from the warehouse that is on the other side of the world. When you deliver the box, I open the box, replace the contents and send it back to the warehouse on the other side of the world. When you come back, I demand that you get the same box again, and you now have to travel to the other side of the world once again... and when you come back the story repeats...
My point here is that it is very wasteful to travel to the other side of the world each time to get the same box, or to get the same element from the DOM at each update. You could cache the elements by looking them up ONE time before you start the update, and you can put the result in an object:
function getElementsToUpdate() {
return {
moneyProductionTotalDisp: getId('moneyProductionTotalDisp'),
moneyDisp: getId('moneyDisp'),
// and so on...
}
}
If you name the property in the object the same as the id, you can put all the names in an array, and then reduce it to an object. This saves you some typing, and hard to find bugs because of a misspelled name:
function getElementsToUpdate() {
return [
'moneyProductionTotalDisp',
'moneyDisp',
'numberDisp',
'scienceDisp',
'popularityDisp',
// and so on....
].reduce(
(out, id) => { out[id] = getId(id); return out; }, {}
)
}
NOTE: This function should be run ONE time at startup, not for every update of the frame. It returns an object with the elements.
Somewhere in your code I assume that you use setInterval to call your update function. Since setInteval can take extra parameters that will be given to the called function, you can do something like this:
var timerHandle = setInterval( update, 1000/7, getElementsToUpdate() );
where update is your function, 1000/7 gives you the interval for 7 times a second, and getElementsToUpdate is the function that makes the time-expensive call to get the elements from the DOM one time.
You need to change the update function to take a parameter (the name is not important, but should be short and descriptive, so I use elem). This is the object that getElementsToUpdate() have returned with all the html-elements.
function update(elem) {
// ... your code ....
// Old code, that makes an expensive lookup into the DOM, and start a HTML parser.
// getId('numberDisp').innerHTML = numFormat(numDisp);
// New code, that gets the pre-looked up element and set the text.
elem.numberDisp.textContent = numFormat(numDisp);
}
I'm not a fan of using .innerHTML when it isn't html that is inserted. Always use .textContent instead, if is isn't html. In this case it would be better if you use the output-element, and set the .value property.
try using console.log(resources["popularity"]["upgProd"])
function append(what) {
$("#drawer").append(what);
}
function outerHtml(o) {
return $("<div />").append($(o).clone()).html();
}
var allPixel = [];
$(".pix").each(function() {
allPixel.push(outerHtml($(this)));
});
$("#drawer").empty();
var index;
for (index = 0; index < allPixel.length; index++) {
pixel = allPixel[index];
setTimeout(append(pixel), 100);
}
I have a Container (#drawer) that is full of many div elements. In this function the HTML of each of these div elements gets saved in an array individually. When its done doing that the div element gets cleared.
In the Array allPixel are now all div elements. I want it so that each div element gets added to #drawer with a delay of 100ms.
Problem:
If I run this function nothing happens (the divs are not disappearing/ appearing). What did I do wrong?
If anything is unclear feel free to let me know and I will edit my question.
Question aswered... See it in action: https://wiese2.lima-city.de/test/
You are invoking the method invoking immediately and passing its return value i.e. undefined to setTimeout.
You can set additional parameters to be passed to append function when timer expires to setTimeout method.
Use
setTimeout(append, 100 * (i + 1), pixel);
Additionally you also need to increase timer based on element index
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.
I have some code that I'm trying to run to get an existing element however it keeps returning null despite it existing in the developer console. I've read some stuff about waiting until the window loads before making a call but the code where I am trying to put this does not run on startup and waits until a user action.
I have even run the following and it returns null:
massText = document.createElement('mass_div');
console.log(document.getElementById('mass_div'));
Can anyone tell me why this returns null? I just created the element so why can I not get it? I have even tried making it initially on start up and then getting it and it still returns null. I would love to be able to edit existing elements rather than keep creating them to just modify their display text which I do by:
massText.innerHTML = "blah";
Here is more context. It's not exactly like this:
//lots of functions that create a model and then call this update method. Called every 500ms
function updateText() {
massText = document.createElement('mass_div');
massText.style.position = 'absolute';
massText.style.width = 100;
massText.style.height = 100;
massText.style.backgroundColor = "green";
massText.innerHTML = "Mass of the star is " + matches[pos] + " Solar Masses";
massText.style.top = window.innerHeight / 10 + 'px';
massText.style.left = window.innerWidth / 50 + 'px';
document.body.appendChild(massText);
}
This creates a ton of copies of the element "mass_div"
I would like to append these by doing something like this however it says massText is null
function updateText() {
if (document.getElementById('mass_div') == null){
massText = document.createElement('mass_div');
}else {
massText=document.getElementById('mass_div');
}
massText.style.position = 'absolute';
massText.style.width = 100;
massText.style.height = 100;
massText.style.backgroundColor = "green";
massText.innerHTML = "Mass of the star is " + matches[pos] + " Solar Masses";
massText.style.top = window.innerHeight / 10 + 'px';
massText.style.left = window.innerWidth / 50 + 'px';
document.body.appendChild(massText);
}
For this to work you should do:
var massText = document.createElement('div');
massText.id='mass_div'
document.appendChild(massText)
console.log(document.getElementById('mass_div')); //This is working
In your code you have 2 issues:
The function createElement get element name not element id. So, you have to add the id later
The function getElementById get only elements that appended to document. So you have to add the element to the document before
Generally (irrespectively if it is java, javascript or c#) when dealing with XML or Html Dom the algorithm is the same and simple:
1. create or load document from string or similar,
2. if no root node (new document) create a root node or find an existing root node,
3. create child node with attibutes and values as you desire (there are multiple constructors),
4. append the child node to the root node (or another child - as you wish).
5. you may fire getElementById or GetElementByTag or similar to find node created in point 3.
You have pasted very little code but I assume that you have skipped step 4 and that is the reason of NRE.
In example the missing parts (should be placed before console.log("aaa"):
Js:
var aaa = document.createElement("mass_div");
document.body.appendChild(aaa);
C#:
var aaa = doc.CreateElement("mass_div");
rootNode.AppendChild(aaa);
Java:
var aaa = doc.createElement("mass_div");
doc.appendChild(aaa)
More or less this should suit your needs and hope it helps.
Kind regards,
P.Sz.
Here we go:
var massText = document.createElement('input'); //Here is you create the element input.
masstext.id ='mass_div'; //Here is you set id for created element input
document.appendChild(massText); //Added to main document
console.log(document.getElementById('mass_div')); //handle your element by id and log
This is working 100% !
Let's say I have a dynamic array that is populated on page load with various amounts of strings inside:
var arr = ["string1","string2","string3","string4","string5","string6","string7","string8","string9","string10","string11","string12","string13","string14","string15","string16","string17","string18"];
I then have a function that is called on each event (let's say a click) that needs to bring back 3 strings of the array consecutively while remembering which value it left off at, the last time it was called. So:
First time function is called, it returns:
string1, string2, string3
Second time it is called, it returns:
string4, string5, string6
and so on...
I don't the need the code for the click event or the callback function, rather the code for the function that would generate the extraction each time and bring it back. Something simple like being able to call:
arr.runExtraction();
on each callback and having it bring back the desired data.
What should happen if the array is exhausted? Start from the beginning?
You could do something like this:
function get_iterator(values, steps, start) {
steps = steps || 1;
var current = start || 0,
max = values.length;
return function() {
var end = current+steps,
end = end > max ? max : end,
t = values.slice(current, end);
current = end % max;
// or if you don't want to wrap around:
// current = end;
return t;
}
}
Edit: Added start parameter.
As current will be the same as values.length in the end, splice will return an empty array if you don't wrap around.
Using slice won't change the original array.
And then
var extract = get_iterator(arr, 3);
var arr1 = extract(); // gives you the first three elements
var arr2 = extract(); // gives you the next three elements etc.
DEMO
It might give you less elements in the "last" extraction if the number of elements is not divisible by number of extracted elements. The next call will let it start from the beginning again. You could also modify it that it wraps around and takes elements from the beginning so that it will always return as many elements as you have specified.
Reference: Array.prototype.slice
var arr = ["string1","string2","string3","string4","string5","string6","string7","string8","string9","string10","string11","string12","string13","string14","string15","string16","string17","string18"];
var runExtraction = function () {
return arr.splice(0, 3);
}