I have the following block of code that I need to complete as quickly as possible:
//someSpans is an array of spans, with each span containing two child spans inside of it
$.each( someSpans, function (i, span) {
//Get the span widths, then add them to the style to make them permanent
var aSpan = span.children[0];
var bSpan = span.children[1];
span.style.width = (aSpan.offsetWidth + bSpan.offsetWidth) + 'px';
aSpan.style.width = aSpan.offsetWidth + 'px';
bSpan.style.width = bSpan.offsetWidth + 'px';
});
If someSpans is an array that contains 1000 objects, this loop presented above will cause 3000 browser redraws, even though nothing on screen is actually changing, since the new "width" attributes in the style match the existing "auto" width. Is there a way to prevent the browser from redrawing the CSS until the loop is finished? I feel like this will greatly reduce the time it takes for the loop to complete.
I feel like requestAnimationFrame might be the key to doing what I'm looking for, but maybe I'm off base.
While the comments of why make a great point, here's a little better answer.
Part of your problem here is the alternating reads/writes from the style. Namely, setting span.style.width has now made aSpan.offsetWidth "dirty" and the CSS must be rendered. However, consider this:
var aWidth = aSpan.offsetWidth;
var bWidth = bSpan.offsetWidth;
span.style.width = (aWidth + bWidth) + 'px';
aSpan.style.width = aWidth + 'px';
bSpan.style.width = bWidth + 'px';
The rendering is now cut down to once per loop. More specifically, it's in reading offsetWidth on the next iteration that causes the render.
Exercise: While it can make code a little more obtuse, sometimes unnecessarily so, I have sometimes written code like this to loop twice. The first time collects the operations into an array, and the second loop is able to combine all the "setting" operations without accessing any layout values.
MSDN has some great documents on JavaScript performance with the most applicable here being "Managing layout efficiently"
Related
I am building an animation with lots of effects and animation in a website. I am trying to optimize the efficiency of those animations since some of them are a little complex to low CPU/RAM devices. The animations are not smooth as I want as you can see here https://www.empresasite.com.br
So I realize in many parts of my code I do this:
var x = 38; //this value changes every 50ms
document.getElementById("a").style.left = document.getElementById("b").style.left = document.getElementById("c").style.left = document.getElementById("d").style.left = document.getElementById("e").style.left = x + "px";
Actually it's a simplification above, I run the code above with at least 13 elements a = b = c = d .... m = x; and at least with 4 properties (left, height, box-shadow and background-color).
So I was wondering if there is any better alternative to assign the same value to multiple elements/objects at once?
Maybe the code below would be faster? I used it but I didnt see a significant improvement in animation, maybe I should have seen it?
var x = 38;
x = x + "px";
document.getElementById("a").style.left = x;
document.getElementById("b").style.left = x;
document.getElementById("c").style.left = x;
document.getElementById("d").style.left = x;
document.getElementById("e").style.left = x;
I think the code above should be faster right? I say this cause every element updates its own property (left) retrieving the value from "x". In the first example I gave MAYBE javascript is assigning the value of the previous element in the chain of "=" sign so it has to assign "x" to the first element then assign the value of the first element to the second... go on.
Do you know exactly what jquery does when I use this:
$(".elements").css({left:x});
Does it use some optimization?
You could increase performance no-end by using translate instead of modifying position.
Using the jQuery variation you asked will not make any difference, as you are still modifying position left.
After playing around with most of the javascript benchmarking tools online I came up to this conclusion:
1) Jquery is the faster method of all native js solutions I used. Maybe Jquery uses some sort of optimization when you apply the same property to lots of elements at once! So you should rely on $(".class").css("left",x) to apply the x value to left property for hundreds of elements and it will be the faster solution;
2) The second solution is apply x individually TO EACH element. Example:
var x = 38;
document.getElementById("a").style.left = x;
document.getElementById("b").style.left = x;
3) The worst solution was actually the one I was using in the website that I was facing not smooth animations. So avoid this:
document.getElementById("a").style.left = document.getElementById("b").style.left = ... = ... = x;
The difference from the 1st solution to the 3rd is really noticeable. You can check it in the links below (pay special attention to the animation at the top):
1) https://www.empresasite.com.br/?q=principal&before -> this is using the 3rd solution
2) https://www.empresasite.com.br/?q=principal -> this is using the 1st solution
In a computer with lots of resources you may not see a difference but if you run this in a computer with 4GB of RAM or less you will see a big impact!
Hope this helps anyone that does not have smooth animations!
I am fetching huge list of people (1000) from server as an API call.
Based on data I have to render "cards" for each user, which will contain their name, picture, age etc.
But when I do that, browser gets stuck for a while till all the cards are rendered. Is there any way to tell browser that its not necessary to render everything at once, it can do so one by one, without crashing itself ?
Take a look here
If you add an element to the DOM, the page will be repainted. If you add 100 elements one by one, it will be repainted 100 times, and that's slow. Create a single container and add nodes for the items to it before appending it to your page.
var container = $('<div>');
$.each(items, function (index, itm) {
var el = $('<div>');
el.attr('id', ID_PREFIX + index);
el.html(createHtml(itm));
container.append(el);
});
$('#list-container').append(container);
Something like the above (using jQuery, but you can be fine with plain JS).
(of course createHtml is an utility function you can define to get the markup for your item and items is the array with your data)
That said, I agree with #Bergi above: do you really need to show all items at the same time? Could you set a scrolling view that populates as you scroll down?
Next thing you can do, use RactiveJs or React to efficiently manage data-binding.
There are a few options here. The best options are, as others have mentioned, not to render at all:
Use an "infinite scroll" technique to only render what you need. The basic idea is to remove DOM elements that go offscreen and add those that come onscreen by inspecting the scroll position.
Use a different user-driven pagination mechanism.
Barring that, you can get better performance by rendering all in one go, either through constructing and setting innerHTML or by using a document fragment. But with 1000's of elements, you'll still get poor performance.
At this point, you probably want batch processing. This won't be faster, but it will free up the UI so that things aren't locked while you're rendering. A very basic batching approach might look like this:
// things to render
var items = [...];
var index = 0;
var batchSize = 100;
// time between batches, in ms
var batchInterval = 100;
function renderBatch(batch) {
// rendering logic here
}
function nextBatch() {
var batch = items.slice(index, index + batchSize);
renderBatch(batch);
index += batchSize;
if (index < items.length - 1) {
// Render the next batch after a given interval
setTimeout(nextBatch, batchInterval);
}
}
// kick off
nextBatch();
It's worth noting, though, that there are limits to this - rendering is a bottleneck, but every DOM element is going to impact client memory too. With 10s of 1000s of elements, things will be slow and unresponsive even after rendering is complete, because the memory usage is so high.
I have an image of a bug. I want to make 5 copies of that image fly in from the side of the screen and bounce around the screen and bounce off the sides. I want them to all have different starting positions and different directions.
so I made some a global variables
var flyVar;
var flySpeed = 5;
var widthMax = 0;
var heightMax = 0;
var xPosition = 0;
var yPosition = 0;
var xDirection = "";
var yDirection = "";
var bugFly;
var count = 1;
var bug = "bug";
I have a function called setBugs() that I use to set the value of widthMax and heightMax depending on the size of the users screen.
I have a bugStartingPlace function to set the initial starting place for each bug. I won't post the whole function but it does the same for "bug1" through "bug5", giving them different values.
function bugStartingPlace(bugName) {
//Accepts string as argument and sets the starting position and starting direction of each bug.
if (bugName == "bug1") {
xPosition = 0;
yPosition = 100;
xDirection = "right";
yDirection = "up";
}
}
I have a function called flyBug() that does the animation and sets the position of the image. It consists of a bunch of statements like this. I know it works because I can make it work with 1 bug. The problem is doing it with 5 bugs.
function flyBug() {
if (xDirection == "right" && xPosition > (widthMax - document.getElementById("bugImage").width - flySpeed))
xDirection = "left";
<!--More flow control statements are here-->
document.getElementById("bug1").style.left = xPosition + "px";
document.getElementById("bug1").style.top = yPosition + "px";
<!-- More statements are here that set the position of the image -->
}
So, I need some way to get the animation going with the body onload() event. One problem is that setInterval does not allow functions that contain parameters. So I can't put multiple statements in the body onload event that pass "bug1" as a parameter to this function, "bug2" as a parameter to this function and so on. That's why I made the global count variable. That way, any time I need to change the name of the bug, I change the name of count and then do
bug = bug + count;
But that adds a lot of complexity. I need the name of the bug for the bugStartingPlace() function, so I need to change the value of count and also change the value of bug before I use that function. Once I use the bugStartingPlace() function, that changes the value of the global variables. Then I need to use flyBug() before I change the value of bug again.
I guess one of the problems is that I'm using global variables for direction and position even though I have multiple bugs. It works fine for one bug but not for multiple bugs.
Can anyone give me tips on how the logic of this program should work?
setInterval allows, like setTimeout, the use of parameters in the function BUT:
setInterval(funcName(param1,param2...), 100);
wont work. Youll get it to work like that:
var func = function () { funcName(param1,param2..); }
setInterval(func, 100);
To understand that part of javascript, read through dougles crockfords explanation of functions, he tells about this very clear and deep. Link to a video of him
EDIT: Sry i understood your question wrong...
The problem why it wont work is, like you figured out the global vars. You could just make bug an object. His actions will then be methods, which can contain a function and so on. If you then initialize a new bug (you can do this a thousand times then), all the vars stay in the object, without conflicting each other. This is a secure way to provide solidness of your code.
You could do it very simple, with nested functions.
Another way would be, to send the name of the bug vie parameter to the, for example, fly function. And the only work in that function with the parameter given to it.
How can I efficiently find all of the DOM elements that are on top of a specified query element?
That is, I want a Javascript function that when I pass in a reference to a DOM element will return an array of all DOM elements that have non-zero overlap with the input element and appear above it visually. My specific goal is to find those elements that may be visually blocking elements below them.
The context is one in which I do not have advanced knowledge of the web page, the query element, or much of anything else. Elements can appear above others for a variety of reasons.
I can of course do this through an exhaustive search of the DOM, but that's very inefficient and not practical when the DOM tree grows large. I could also use the newer elementFromPoint to sample positions from within the query element to ensure that it is indeed on top, but that seems pretty inefficient.
Any ideas on how to do this better?
Thanks!
I cannot think of a simpler way than using elementFromPoint. You don't seem to want to use it but can give you some consistent result.
If there are multi layered elements, you should adapt your code to move already grabbed elements or set them invisible and recall function to get new set of data elements.
For the basic idea:
function upperElements(el) {
var top = el.offsetTop,
left = el.offsetLeft,
width = el.offsetWidth,
height = el.offsetHeight,
elemTL = document.elementFromPoint(left, top),
elemTR = document.elementFromPoint(left + width - 1, top),
elemBL = document.elementFromPoint(left, top + height - 1),
elemBR = document.elementFromPoint(left + width - 1, top + height - 1),
elemCENTER = document.elementFromPoint(parseInt(left + (width / 2)), parseInt(top + (height / 2))),
elemsUpper = [];
if (elemTL != el) elemsUpper.push(elemTL);
if (elemTR != el && $.inArray(elemTR, elemsUpper) === -1) elemsUpper.push(elemTR);
if (elemBL != el && $.inArray(elemBL, elemsUpper) === -1) elemsUpper.push(elemBL);
if (elemBR != el && $.inArray(elemBR, elemsUpper) === -1) elemsUpper.push(elemBR);
if (elemCENTER != el && $.inArray(elemCENTER, elemsUpper) === -1) elemsUpper.push(elemCENTER);
return elemsUpper;
}
jsFiddle
It's unfortunate but there is no way to have a solution that will not iterate through all DOM element, because you can put any element anywhere on screen through CSS rules.
The best you can do it actually iterating over all the DOM elements to make a hit test.
If I had to do this, I would rely on jQuery, which is a widely used cross-browser API under constant improvement.
Take a look at http://api.jquery.com/position/ , http://api.jquery.com/width/ and http://api.jquery.com/height/
If performance is very important, you can gain a factor by diving into their implementation and improving it for your specific case, but keep in mind that the complexity will not go below O(number of DOM elements)
I have build a grid of div's as playground for some visual experiments. In order to use that grid, i need to know the x and y coordinates of each div. That's why i want to create a table with the X and Y position of each div.
X:0 & Y:0 = div:eq(0), X:0 Y:1 = div:eq(1), X:0 Y:2 = div:eq(2), X:0 Y:3 = div:eq(3), X:1 Y:0 = div:eq(4) etc..
What is the best way to do a table like that? Creating a OBJECT like this:
{
00: 0,
01: 1,
02: 2,
etc..
}
or is it better to create a array?
position[0][0] = 0
the thing is i need to use the table in multiple way's.. for example the user clicked the div nb: 13 what are the coordinates of this div or what is the eq of the div x: 12 y: 5.
Thats how i do it right now:
var row = 0
var col = 0
var eq = 0
c.find('div').each(function(i){ // c = $('div#stage')
if (i !=0 && $(this).offset().top != $(this).prev().offset().top){
row++
col = 0
}
$(this).attr({'row': row, 'col': col })
col++
})
I think it would be faster to build a table with the coordinates, instead of adding them as attr or data to the DOM. but i cant figure out how to do this technically.
How would you solve this problem width JS / jQuery?
A few questions:
Will the grid stay the same size or will it grow / shrink?
Will the divs stay in the same position or will they move around?
Will the divs be reused or will they be dynamically added / removed?
If everything is static (fixed grid size, fixed div positions, no dynamic divs), I suggest building two indices to map divs to coordinates and coordinates to divs, something like (give each div an id according to its position, e.g. "x0y0", "x0y1"):
var gridwidth = 20, gridheight = 10,
cells = [], // coordinates -> div
pos = {}, // div -> coordinates
id, i, j; // temp variables
for (i = 0; i < gridwidth; i++) {
cells[i] = [];
for (j = 0; j < gridheight; j++) {
id = 'x' + i + 'y' + j;
cells[i][j] = $('#' + id);
pos[id] = { x: i, y: j };
}
}
Given a set of coordinates (x, y) you can get the corresponding div with:
cells[x][y] // jQuery object of the div at (x, y)
and given a div you can get its coordinates with:
pos[div.attr('id')] // an object with x and y properties
Unless you have very stringent performance requirements, simply using the "row" and "col" attributes will work just fine (although setting them through .data() will be faster). To find the div with the right row/col, just do a c.find("div[row=5][col=12]"). You don't really need the lookup.
Let me elaborate on that a little bit.
If you were to build a lookup table that would allow you to get the row/col for a given div node, you would have to specify that node somehow. Using direct node references is a very bad practice that usually leads to memory leaks, so you'd have to use a node Id or some attribute as a key. That is basically what jQuery.data() does - it uses a custom attribute on the DOM node as a key into its internal lookup table. No sense in copying that code really. If you go the jQuery.data() route, you can use one of the plugins that allows you to use that data as part of the selector query. One example I found is http://plugins.jquery.com/project/dataSelector.
Now that I know what it's for...
It might not seem efficient at first, but I think It would be the best to do something like this:
Generate the divs once (server side), give them ids like this: id="X_Y" (X and Y are obviously numbers), give them positions with CSS and never ever move them. (changing position takes a lot of time compared to eg. background change, and You would have to remake the array I describe below)
on dom ready just create a 2D array and store jquery objests pointing the divs there so that
gridfields[0][12] is a jQuery object like $('#0_12'). You make the array once and never use selectors any more, so it's fast. Moreover - select all those divs in a container and do .each() on them and put them to proper array fields splitting their id attributes.
To move elements You just swap their css attributes (or classes if You can - it's faster) or simply set them if You have data that has the information.
Another superfast thing (had that put to practice in my project some time ago) is that You just bind click event to the main container and check coordinates by spliting $(e.target).attr('id')
If You bind click to a grid 100x100 - a browser will probably die. Been there, did that ;)
It may not be intuitive (not changing the div's position, but swapping contents etc.), but from my experience it's the fastest it can get. (most stuff is done on dom ready)
Hope You use it ;) Good luck.
I'm not 100% sure that I understand what you want, but I'd suggest to avoid using a library such as jQuery if you are concerned about performance. While jQuery has become faster recently, it still does has more overhead than "pure" JS/DOM operations.
Secondly - depending on which browsers you want to support - it may even be better to consider using a canvas or SVG scripting.