Given a web page, how do you find the largest rectangle on the webpage which is the main content area?
For example, compare the size of sidebar, header, footer, and main content area. Is it possible to find the main content area by simply searching for the largest rectangle out of all the rectangles discovered on a page?
Usually the tallest and widest rectangle is suspected to be the main content area, wondering if there's an algorithm of some sort in Javascript or Python to test this hypothesis out.
So while the question didn't make much sense to me, I couldn't resist the urge to toy around with the concept of recursively scanning a DOM tree to retrieve and sort elements by their sizeĀ :)
Here's a dumb function for doing so (you can paste it in your browser console):
function scanSizes(root) {
return [].reduce.call(root, function(sizes, node) {
var bounds = node.getBoundingClientRect();
sizes.push({tag: node.outerHTML, area: bounds.width * bounds.height});
var children = node.querySelectorAll("*");
if (children.length > 0)
sizes.push.apply(sizes, scanSizes(children));
return sizes;
}, []).sort(function(x, y) {
var a = x.area, b= y.area;
return a > b ? -1 : a < b ? 1 : 0;
});
}
var sizes = scanSizes(document.querySelectorAll("body > *"));
// sizes[0].tag contains the largest html tag (as a string)
// sizes[0].area its area size in pixels (width * height)
Edit: more seriously, you might be interested in this topic and related answers.
Edit: of course performance wise recursion wasn't a really good idea. You can go with something like this to have a more efficient solution:
function scanSizes(root) {
return [].map.call(root, function(node) {
var bounds = node.getBoundingClientRect();
return {tag: node.outerHTML, area: bounds.width * bounds.height};
}).sort(function(x, y) {
var a = x.area, b= y.area;
return a > b ? -1 : a < b ? 1 : 0;
});
}
var sizes = scanSizes(document.querySelectorAll("*"));
I'm adding another answer because I've just stumbled upon the <main> HTML5 element spec, which developers are supposed to use to define their main contents area, so that's probably the very first element you'll want to check for in any scraped page.
So basically you should check for any single <main> or role="main" element in the page, then only use other contents detection strategiesĀ :)
The current answer is overly complex. The main thing you need to know is element.getBoundingClientRect();. Here's a smaller function - I'm looking for the biggest table but you can use any CSS selector you want.
// Fix NodeList.sort()
NodeList.prototype.sort = Array.prototype.sort
var elements = document.querySelectorAll('table')
var getArea = function(element){
var rectangle = element.getBoundingClientRect();
return rectangle.width * rectangle.height;
}
elements.sort(getArea)[0]
Related
I am making a simple game in js which consists of a grid and some cells
This is what it currently looks like and it works perfectly. The thing is that while making this I set the margin to 0 but I
would like to move the canvas to center. To do that, I got the margin from this function
var test = document.querySelector('.test');
var left_margin = window.getComputedStyle(test).getPropertyValue("margin-
left "); // returns margin e.g. '655px'
left_margin = left_margin.match(/\d+/);
Then I made some changes in the whole thing to account for this offset but for some reason when I try to add the offset to the x-axis, it returns NaN. To make sure there wasnt some problem I performed some basic mathematical operations on this value and they worked. Can someone tell me what is going on with this? and also, is there a simple way to just redefine the origin for an element(a canvas in my case), to avoid this hassle?
Edit:
I dont understand this. When i simply do var a = blockWidth + 0;The game doesnt start and then do console.log(a) this also return NaN. and i get**(Uncaught TypeError: Cannot set property 'strokeStyle' of undefined)**
function grid(){
var a = blockWidth + left_margin;
var b = blockHeight;
while (a != widthWin){
drawLine(a, 0, a, heightWin, 1, 'gray');
a += blockWidth;
}
while (b != heightWin){
drawLine(left_margin, b, widthWin+left_margin, b, 1, 'gray');
b += blockHeight;
}
}
What you get with
left_margin = left_margin.match(/\d+/);
is an array ["655"] not an int as you are assuming.
Therefore, you need to access its first element and parse it to a number before using it for doing any math:
left_margin = parseInt(left_margin.match(/\d+/)[0]);
I am trying to create a grid of squares that could fit in any dimensions container (ie. the squares resize themselves or add/delete new ones if the container width/height are changed).
I am going the Javascript route for now (and Jquery, for now) - there might be a flexgrid solution but since I plan to populate my squares with some kind of cellular automata type thingie, I figured that it wouldn't hurt. This doesn't solve my problem, since the number of lines of squares seems to be fixed.
Here is what I have so far:
var screen = {
width: 0, // these I get with jquery, on load and on resize events
height: 0
}
var values = {
min: 100, // minimum square size
max: 500 // maximum square size
}
var findSize = function() {
var r = 1;
var currVal = values.min;
while (r > 0) {
if((screen.width % currVal) === (screen.height % currVal)) {
// this should mean that they are both divisible by this value, right?
// get out of the loop and return value
r = 0;
return currVal;
} else if (currVal > values.max ) {
// if we exceed the maximum size, get out of the loop and return 0
r = 0;
return 0;
} else {
// if not, increment a bit
currVal += 0.25; // increment the value to check the modulo against
}
}
}
Calling the findSize() function should return either the dimensions of the square (from which I can then build my grid easily, with either floated squares or absolutely positioned ones.
The problem is that it doesn't. Well it sometimes does. And it also pretty often gives me nothing...
The border are done with box-shadow so it shouldn't affect the dimensions.
So I am wondering...
Where is my code faulty?
Can I change my function so it return always something (maybe with smaller incrementations?). I can work with rounded values for display purpose.
The brute force aspect doesn't seem too efficient. Is there a way to refactor this so the loop is shorter?
Thanks a lot!
The problem with this code is it tries to find the solution but fails to account for answers between each deltas (0.25).
Also, if there can be any numbers of cells (added removed automatically) then the answer can also be "always 100".
I guess what you're trying to achieve here is a "best fit" that could leave no borders horizontally and vertically at the same time. I'm not sure if there is a proper answer to that question (you probably need to search for Greatest common divisor, close to what you are doing), and I wonder if something like the code below wouldn't work in your case:
var findSize = function() {
var ratio = screen.width / screen.height; // get the ratio between width and height
if (ratio > 1) {
ratio = 1 / ratio; // always have it always <= 1
}
var size = values.max * ratio; // size between 0 and max
if (size < values.min) {
return values.min; // failed, could try other things here
}
return size; // size between min and max
}
I try to find all images in a web page, add them in an array, check their dimensions and sort the array accordingly.
var images = document.getElementsByTagName("img");
var image_dimensions=[];
for(var i=0;i<images.length;i++){
image_dimensions.push(images[i].width+"x"+images[i].height);
}
//var image_dimensions = ["45x67", "68x45", "12x23", "124x90"];
I want to sort the images according to their dimensions and update the images array. I tried to push dimensions into image_dimensions and sort it but later I could not figure out how can I relate the dimensions to the actual img nodes.
How can I sort the images in a web page by dimension?
From the comments you've made I think what you actually want to sort by is the total area of each image (height * width). You can do this by using the sort method with a custom compare function.
var sortedImgs,
imgs = document.getElementsByTagName('img'); // returns a NodeList,
// an array-like object
imgs = [].slice.call(imgs); // convert into a real array
sortedImgs = imgs.sort(function (a, b) {
// Compute the area of each image
var aArea = a.width * a.height,
bArea = b.width * b.height;
// compare the area of each
return aArea - bArea;
});
The compare function subtracts the area of b from a and returns that value to the sort function:
If they have the same area this will return 0, telling sort they are equal
I If b is bigger than a, a negative number will be returned telling sort to sort a lower.
If a is bigger than b, a positive number will be returned telling sort to sort b lower.
Swaping aArea and bArea would flip those results, reversing the sort order.
The other answers have used clientWidth and clientHeight for some reason; clientWidth and clientHeight include any padding that might be on the element. (And as crazy as it is, images can have padding.) I used .width and .height, assuming you are interested in the actual dimensions of the image itself, not any styling that has been added to it.
Use a 2D array and include the image element too, something like this:
var images = document.getElementsByTagName('img');
var imageDimensions = [];
for(var i = 0; i < images.length; i++) {
imageDimensions.push([images[i].clientWidth, images[i].clientHeight, images[i]]);
}
imageDimensions.sort(function (a, b) {
if (a[0] - b[0] === 0) {
return a[1] - b[1];
}
return a[0] - b[0];
});
clientWidth/Height returns the size of the image as you can see on the page.
Result:
imageDimensions = [
[width, height, HTMLImageElement],
[width, height, HTMLImageElement],
:
]
And as adeneo has mentioned in a comment, you've to do this after the images have been fully loaded, put the code for example into window.onload event handler.
You can use clientWidth and clientHeight property to find the dimension as described here ..After finding the dimensions you can use the function .sort() for the sorting stuff.
Please have a look at this link for the reference.
How can I prevent this map generator from creating touching corners like this:
-X
X-
Or
X-
-X
Here is a simplified example of the generator: http://jsfiddle.net/fDv9C/2/
Your question answers itself, almost.
Here's the fiddle: http://jsfiddle.net/qBJVY/
if (!!grid[y][x] && !!grid[y+1][x+1] && !grid[y+1][x] && !grid[y][x+1]) {
good=false;
grid[y+1][x]=2;
}
It simply checks for the combinations you do not want and patches them up. It always adds a grid point so as not to disconnect any parts of the map.
This in turn may lead to another situation where the issue may occur, but if it changed anything (that is, if it found a problem), it will simply check again. This can be optimized, for instance by recursively adjusting whatever was changed, but usually it only needs 1 or 2 passes. There's a limiter on there to not allow more than 100 passes, just in case there is some unforeseen circumstance in which it cannot fix it (I can't think of such a situation, though :) ).
Because of the way that you are creating board it's very difficulty to do this checking during generation. I create simple function that check board after. It's using flood algorithm. Here is the fiddle http://jsfiddle.net/jzTEX/8/ (blue background is original map, red background is map after checking)
Basically we create second array grid2. After filling grid we run recursively floodV function
function floodV(x,y) {
var shiftArray = [[0,1],[0,-1],[1,0],[-1,0]];
grid2[y][x]=1;
for(var k=0;k<4;k++) {
var x1=x+shiftArray[k][0];
var y1=y+shiftArray[k][1];
if(grid[y1][x1] == 1 && grid2[y1][x1] == 0 && checkV(x1,y1)) {
grid2[y1][x1] = 1;
floodV(x1,y1);
}
}
}
with the check function
function checkV(x,y) {
var checkVarr = [[-1,-1], [-1,1], [1,1], [1,-1]];
for(var k=0;k<4;k++) {
if(grid[y+checkVarr[k][0]][x+checkVarr[k][1]] == 1 && grid[y+checkVarr[k][0]][x] == 0 && grid[y][x+checkVarr[k][1]] == 0 && grid2[y+checkVarr[k][0]][x+checkVarr[k][1]] == 1)
return false;
}
return true;
}
This isn't perfect because we can sometimes throw away big parts of the map but if we try to start adding new elements we have to check whole map again (in worths case).
This is what I did: http://jsfiddle.net/fDv9C/13/
Where's the magic happening? Scroll down to lines 53 through 58:
var bottom = y_next + 1;
var left = x_next - 1;
var right = x_next + 1;
var top = y_next - 1;
if (grid[top][left] || grid[top][right] ||
grid[bottom][left] || grid[bottom][right]) continue;
In short your touching corner points can only occur at the computed next position. Hence if any one of the four corner neighbors of the next position exists, you must compute another next position.
You may even decrement the counter i when this happens to get as many paths as possible (although it doesn't really make a big difference):
var bottom = y_next + 1;
var left = x_next - 1;
var right = x_next + 1;
var top = y_next - 1;
if (grid[top][left] || grid[top][right] ||
grid[bottom][left] || grid[bottom][right]) {
i--;
continue;
}
See the demo here: http://jsfiddle.net/fDv9C/12/
Edit: I couldn't resist. I had to create an automatic map generator so that I needn't keep clicking run: http://jsfiddle.net/fDv9C/14/
In the following picture:
alt text http://rookery9.aviary.com.s3.amazonaws.com/4478500/4478952_3e06_625x625.jpg
I want to connect the boxes in the above with below, Let us call the bottom edge of the top boxes as A and top edge of the below boxes as B
Now, I have two arrays containing the points in the line A and B say
A = [ {Ax1, Ay1},{Ax2, Ay2},.... ] and B = [ {Bx1, By1},{Bx2, By2},.... ]
In real world it can be like A = [ {100, 100},{120, 100},{140, 100},{160, 100}] and B=[ {120, 200},{140, 200},{160, 200},{180, 200},{200, 200},]
Please look at the black dots in the picture above
How can I get the connectiong points as shown in the pictures? Connecting point must be as close to the center of the line as possible.
Here is what I'm trying to get, but below functions draw line between the two matching points from the starting from the left of the both lines, Any suggessions
drawConnection : function(componentOut, componentIn, connectionKey) {
var outDim = $(componentOut).data('dim');
var inDim = $(componentIn).data('dim');
var outPorts = $(componentOut).data('ports');
var inPorts = $(componentIn).data('ports');
var abovePorts = {};
var belowPorts = {};
var i = 0;
if(outDim.bottomLeft.y < inDim.topLeft.y){
// Now proceed only if they can be connect with a single line
if(outDim.bottomLeft.x < inDim.topRight.x && outDim.bottomRight.x>inDim.topLeft.x) {
// Now get a proper connecting point
abovePorts = outPorts.bottom;
belowPorts = inPorts.top;
for(i=0; i<abovePorts.length; i++) {
for(j=0; j<belowPorts.length; j++) {
if(!abovePorts[i].inUse && !belowPorts[j].inUse && (abovePorts[i].x == belowPorts[j].x)){
console.debug("Drawing vertical lines between points ("+abovePorts[i].x+","+abovePorts[i].y+") and ("+abovePorts[i].x+","+belowPorts[j].y+")");
return true;
}
}
}
}
}
return false;
},
-- Update
I'm exactly trying to get something similar to this http://raphaeljs.com/graffle.html, but the connections should be made with straight lines as shown below
alt text http://rookery9.aviary.com.s3.amazonaws.com/4480500/4480527_1e77_625x625.jpg
Have you tried Raphael.js : http://raphaeljs.com/ ?
Another approach is to use the HTML+CSS engine of the browser, instead of using JS.
You can use a table.
One cell row for each box and a 2 cells row for the connector.
You color one of the border for the connector and use margin, float and width styles, to position the boxes.
I've already used this technique to draw org charts a long time ago... when IE6 was considered the best browser!
Another worth looking at is Processing.js if you want a bit more power. I've used Raphael.js before and that was pretty easy to pickup and use. Just be aware that both utilize the Canvas element which to my knowledge isn't supported in all browsers yet.