get offset of each DOM element efficiently - javascript

In order to determine the page margin in various positions on the page I need to get the offset, width and height of each DOM element.
I loop over the DOM elements recursively and save each element's attributes.
I tried it with JQuery $(el).offset() but it was very slow. I guess that creating a JQuery object for each element by using the$(...) is very slow.
Then, I tried an old implementation that I got from an old code which uses native JS and it was X4 times faster.
SO what I'm really asking, is there a different method to accomplish that?
I should mention that my script runs on publishers' sites and I don't really know anything about the page that I'm running on.

I guess this is one solution, using map() and getBoundingClientRect():
var elements = document.getElementsByClassName('someclass');
elements.map(function(val, i, arr) {
var doc = document.getBoundingClientRect(),
el = val.getBoundingClientRect(),
coordsX = el.left - doc.left,
coordsY = el.top - doc.top;
return {x: coordsX, y: coordsY};
});
This will return an array of all the coordinates of a given class. You can change document.getElementsByClassName to querySelector if it better suits your needs.
This will also change getBoundingClientRect() to be relative to the document instead of the viewport.

Related

Should I use a for loop to iterate over my object's array?

I have created a DomObject class that can display boxes that move around.
jFiddle
Naturally, if I increase the number of boxes in the DomObject, the movement function takes longer to run since it is a for loop.
beginMovement = () => {
if (!this.timer) {
// console.log("Begin Movement");
this.timer = setInterval(this.movement, this.time);
}
};
movement = () => {
// here we know that its running
let i = 0;
while (i < this.boxes.length) {
this.boxes[i].move();
i++;
}
}
In the case that I increase the length of this.boxes, I notice that there are performance issues.
So my main question is, should I be using a for loop in this instance? Or should I avoid using basic html for moving items all together and move onto using canvas
Depends what your goal is. You seem to be trying to do some sort of an animation in which case using canvas/WebGL is a better option if you are going for raw speed.
Now your objects are living on the DOM, which wasn't originally designed to display fancy graphics and animations. Every design consideration for canvas was made explicitly for animation and complex bitmap operations like colorization and blurring. You never have to "find" a canvas element. You can access them far more quickly than even a cached DOM element. Even updating text elements in canvas is faster then doing so on the DOM.
Here is a related SO discussion on canvas vs DOM.

How to delay rendering of DOM element to prevent unresponsiveness

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.

Efficiently Finding DOM Elements Appearing over a Specified DOM Element

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)

Faster SVG Path manipulation

So I want to make a drawing tool using SVG, I'm using a rather naive approach to change the d attribute of my Path:
$("div#drawarea").bind("mousemove", function(ev) {
ev.preventDefault();
ev.stopPropagation();
var pX= (ev.pageX - this.offsetLeft);
var pY= (ev.pageY - this.offsetTop);
$path.attr("d", $path.attr("d") + " L" +pX+ "," + pY); //using jquery-svg here to change the d attribute
});
As you can see I do this on the mousemove function. The code works but it becomes unresponsive when the mouse is moving fast creating numerous straight lines when I actually want it to be smooth lines. I think this is happening because the numerous string concatenations I'm doing on the mousemove event (the d attribute on the path can become quite big when the click has been held for long, thousands of characters long in fact).
I'm wondering if there is any native way to add new values at the end of a path instead of manipulating the d attribute directly. I checked the jquery-svg sourcecode and it seems that the library also uses the naive string concatenation mode internally so using its methods would not wield any benefit.
Also I'm wondering if this is the case or if the browser just limits the amount of mousemove events (once every X milliseconds?) that can be triggered and so no performance optimizations would improve this.
Use the SVG pathseg DOM methods. You have to write more complicated code but the browser doesn't have to reparse the whole path attribute. Firefox for instance does take advantage of this and it's quite likely other broswers also.
In case someone else stumbeld upon the quesion of what is the fastes way to update an SVG-Path data attribute (for realtime applications), I run a small test on that:
http://jsperf.com/svg-path-test
Yes, setting it as string means that it needs to be parsed, which isn't the case for the DOM SVG interface but the first method is still much faster. Maybee the interface updates the DOM with each point added, slowing down the whole process.

How do i make a allocation table?

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.

Categories

Resources