Fill window with divs. Div pixel height displayed incorrectly in google chrome. Width works - javascript

I want to fill the window size with divs. For a specified div size in px, the screen will be filled as much as it can be, leaving a remainder edge amount of px on the side and bottom. This remainder amount is then divided by the number of cells in the row (or column) and that is then added to the height (or width) of each cell in the row (or column).
For the width this works perfectly but when the same logic is applied to the height, it breaks. Both width and height work in firefox.
Screenshot: http://i.imgur.com/mpDCM0G.png
JSfiddle of making the divs: https://jsfiddle.net/xb82c4zt/
Live: http://conwaygameoflife.heroku.com/
var windowWidth = window.innerWidth;
var windowHeight = window.innerHeight;
var size = 100;
// Calculate the number of cells we can fit in the width
//and height (there will be extra space)
w = Math.floor(windowWidth / size);
h = Math.floor(windowHeight / size);
// Calculate the extra space
var widthDiff = windowWidth % size;
var heightDiff = windowHeight % size;
// Add the needed amount of height and width to each cell to fill the window
var widthSize = size + widthDiff / w;
var heightSize = size + heightDiff / h;
// Begin to alter the DOM
var parentDiv = document.createElement('div');
parentDiv.className = 'grid';
for(var y = 0; y < h; y++) {
for(var x = 0; x < w; x++) {
var cellDiv = document.createElement('div')
cellDiv.className = 'cellDiv'
cellDiv.style.height = heightSize + 'px';
cellDiv.style.width = widthSize + 'px';
parentDiv.appendChild(cellDiv)
}
}
document.body.appendChild(parentDiv)

In Chrome (and probably other browsers), height and width pixel values are truncated! See this stackoverflow answer with the related jsFiddle
Precentage values are truncated too, but not as severely. So, to solve this you can convert pixels to percentages as I did in this jsFiddle.
The main thing I added was:
var widthPercent = widthSize / windowWidth * 100;
var heightPercent = heightSize / windowHeight * 100;
Because we're using percentages now, the parent container must have width/height:
parentDiv.style.height = windowHeight + 'px';
parentDiv.style.width = windowWidth + 'px';
And changed the loop to:
for(var x = 0; x < w*h; x++) {
var cellDiv = document.createElement('div');
cellDiv.className = 'cellDiv';
cellDiv.style.height = heightPercent + '%';
cellDiv.style.width = widthPercent + '%';
parentDiv.appendChild(cellDiv)
}
Now this doesn't always work in chrome perfectly. However, it does make it perfect in some cases... basically depends on when (and how drastic) the truncation of percentages is.
After further reflection, it looks like percentages get resolved to fractional pixel values as well... which still get truncated in Chrome. So, let's make our math better, and figure out the biggest non-fractional pixel value we can use... it's actually really easy. See here
Basically, we just floor the values, then center the grid so that we can make it look nice.
edit: wasn't very happy with this answer, so screwed with it some more. Added a function that found the closest multiple of window size and made it so that it would prefer that number. Makes it work in most screen sizes, and has a fallback to the percentage method if it doesn't perfectly work. See here. However, because it relies on a recursive (naive) algorithm to find the closest multiple, it's really easy to screw your browser performance. Limiting to only 5-10 pixels of search space helps. The gist of it:
function closestMultiple(width, size, n, limit) {
if(n > limit) {
return {m: width/size, s:size};
}
if((width % (size+n)) == 0) {
return {m: width / (size+n), s: size+n};
} else if((width % (size-n)) == 0) {
return {m: width / (size-n), s: size-n};
}
return closestMultiple(width, size, n+1, limit);
}
It's very naive and ignores things like "an odd width will never be divisible by an even number"... so there's a ton of room for improvement. Check out this discussion and this discussion for more on this.

Related

Javascript scale images to fit container and to equal height

I'm trying to build a gallery. The idea is to fit images into fixed width container, but images must be of the same height and preserve original aspect ratio, so they just need to scale somehow.I came up with a solution of my own, but it, sometimes,gives clunky results for images total width that are too small or too large to fit container.Also resulting widths are, for some reason, floating point values. Could someone help me to figure out more optimal way to do it?
My clunky solution: https://codepen.io/fuzzy-toozy/pen/wvEaorW
function recalcGallery() {
if (uploadedImages.length > 0) {
let min = galleryHeight;
for (let i = 0; i < uploadedImages.length; i++ ) {
let currentUploadedImages = uploadedImages[i];
// find element with smallest height
currentUploadedImages.forEach((val) => { if (min > val.height) { min = val.height; }});
let imgCont = [];
let totalWidth = 0;
// set all elements to same height
for (let j = 0; j < currentUploadedImages.length; j++) {
let imgContainer = document.querySelector(`[image-index${i}="${j + 1}"]`);
imgContainer.style.height = `${min}px`;
imgCont.push(imgContainer);
totalWidth += imgContainer.clientWidth;
}
if (totalWidth > galleryWidth) {
// calculate value to decrease height by based on percent of overflow
let decPx = Math.ceil(min - min * (galleryWidth) / totalWidth);
imgCont.forEach((val, i) => {
val.style.height = `${val.clientHeight - decPx}px`;
});
}
}
}
}
Your solution involves finding the element with the smallest height, setting all elements to the same height, and then checking if the total width of the images is greater than the gallery width. If the total width is greater, you calculate a value to decrease the height by based on the percentage of overflow and then decrease the height of each image container.
One potential issue with your solution is that you are relying on the clientWidth property of the image containers to calculate the total width of the images. This property can include padding and borders, which may not accurately reflect the total width of the images. A more accurate approach would be to use the naturalWidth property of the image elements, which reflects the actual width of the image.
Here is an example of how you could modify your code to use the naturalWidth property:
function recalcGallery() {
if (uploadedImages.length > 0) {
let min = galleryHeight;
for (let i = 0; i < uploadedImages.length; i++ ) {
let currentUploadedImages = uploadedImages[i];
// find element with smallest height
currentUploadedImages.forEach((val) => { if (min > val.height) { min = val.height; }});
let imgCont = [];
let totalWidth = 0;
// set all elements to same height
for (let j = 0; j < currentUploadedImages.length; j++) {
let imgElement = document.querySelector(`[image-index${i}="${j + 1}"] img`);
let aspectRatio = imgElement.naturalWidth / imgElement.naturalHeight;
let imgContainer = document.querySelector(`[image-index${i}="${j + 1}"]`);
imgContainer.style.height = `${min}px`;
imgContainer.style.width = `${min * aspectRatio}px`;
imgCont.push(imgContainer);
totalWidth += imgContainer.clientWidth;
}
if (totalWidth > galleryWidth) {
// calculate value to decrease height by based on percent of overflow
let decPx = Math.ceil(min - min * (galleryWidth) / totalWidth);
imgCont.forEach((val, i) => {
val.style.height = `${val.clientHeight - decPx}px`;
val.style.width = `${(val.clientHeight - decPx) * (val.querySelector('img').naturalWidth / val.querySelector('img').naturalHeight)}px`;
});
}
}
}
}
In this modified code, we calculate the aspect ratio of each image using the naturalWidth and naturalHeight properties, and set the width of each image container accordingly. We then use the clientWidth property of the image containers to calculate the total width of the images, but we use the naturalWidth property of the image elements to calculate the width of each image container when we need to adjust the height of the images.
Note that this code assumes that the images are contained within an tag within each image container. If you are using a different approach to display the images, you may need to modify the code accordingly.

Resize a group of variably sized images both horizontally & vertically - need help optimising

WARNING CODE CRASHES IN EVERYTHING EXCEPT GOOGLE CHROME
I'm trying to create a feature on our website that takes 8 random images and places them in two rows and dynamically resizes the images to take up the full width of the page.
I've created a jsbin for this to try and demonstrate the issue.
https://jsbin.com/yijemazovi/edit?html,css,js,output
The comments in the code should give you an good idea of what I'm doing. What seems to be happening for everything but Google Chrome is that the while condition is never satisfied so it goes on infinitely and crashes the browser.
Perhaps it is something as simple as I am doing the do/while loop incorrectly or I should just be using a while loop???
Any help is appreciated!
/*****
* Get the overall width of the container that we want to match
**********/
var ContainerWidth = $('.feature-inim-collage .col.span_1_of_1').width();
/*****
* Increase the height of the images until the total sum of the width
* if the 4 images + the gutters is larger than ContainerWidth - then
* stop
**********/
/*****
* Increment in jumps of 10px until we get within 80% of the width of
* the ContainerWidth and then go to a more precise increment of 1px.
* We can increase the px from 10 to 20 or 30 so there are less loops
* but this can cause issues when we look at mobile and there is less
* overall width in the containers and jumping by 30px will be too much
**********/
var i = 0;
do {
$('.feature-inims-top-row .growable-container').css('height', i);
var RowWidth1 = CalculateTotalWidth(1);
if(RowWidth1 < (ContainerWidth*0.8)){
i = i+10;
}else{
i++;
}
}
while (RowWidth1 < (ContainerWidth - 3));
/*****
* Repeat above for the 2nd row
**********/
var i = 0;
do {
$('.feature-inims-bottom-row .growable-container').css('height', i);
var RowWidth2 = CalculateTotalWidth(2);
if(RowWidth2 < (ContainerWidth*0.8)){
i = i+10;
}else{
i++;
}
}
while (RowWidth2 < (ContainerWidth - 3));
/*********
* Calculate the combined width of the images + the gutters
****/
function CalculateTotalWidth(Row) {
var Image1Width = $('.growable-container-1').width();
var Image2Width = $('.growable-container-2').width();
var Image3Width = $('.growable-container-3').width();
var Image4Width = $('.growable-container-4').width();
var Image5Width = $('.growable-container-5').width();
var Image6Width = $('.growable-container-6').width();
var Image7Width = $('.growable-container-7').width();
var Image8Width = $('.growable-container-8').width();
var GutterSize = 24; // (3 gutters # 8px each)
if(Row == 1){
var RowWidth = GutterSize + Image1Width + Image2Width + Image3Width + Image4Width;
}else{
var RowWidth = GutterSize + Image5Width + Image6Width + Image7Width + Image8Width;
}
return RowWidth
}
It turns out the issue with this was that in the CalculateTotalWidth() function I was checking the width of the container the image was in rather than the image itself. As soon as I changed this it worked perfectly.
var Image1Width = $('.growable-container-1 img').width();
instead of
var Image1Width = $('.growable-container-1').width();

javascript game sprite positioning

I'm trying to create a chess board, and place it in the middle of the screen, so far i cannot get it to be directly in the center. i don't want to hard code the position to the screen because i'm going to be dealing with different screen sizes.
var winsize = cc.director.getWinSize();
var centerpos = cc.p(winsize.width / 2, winsize.height / 2);
for (i=0; i<64; i++){
var tile = cc.Sprite.create(res.tile_png);
this.addChild(tile,0);
tile.setPosition(winsize.width+i%8*50/-10, winsize.height-Math.floor(i/8)*50);
}
But the tiles and positioning is completely off
#jumpman8947, if you're using Cocos2d js perhaps you have a similar line: cc.view.setDesignResolutionSize(480, 320, cc.ResolutionPolicy.SHOW_ALL);
In this particular case the game will scale to any sceeen, but still run in 480x320 resolution, so no matter what screen resoultion you use, the center in the cocos world would always be cc.p(240, 160) so no matter what's the window size or the screen resolution, the resolution of the game stays the same
You can read more about resolution policies here (and in official js-doc):
http://www.cocos2d-x.org/wiki/Multiple_Resolution_Policy_for_Cocos2d-JS
Also please be aware, that the Sprite position in Cocos is the position of the centre of the sprite, not bottom left corner
In your question it's not completely clear exactly what you want. However, I made some assumptions. The explanation for my solution is embedded in the comments in the code below.
// var winsize = cc.director.getWinSize();
// Here is some example hard-coded return values:
var winsize = {width: 600, height: 400};
// You can change these numbers to see how they influence
// the outcome.
// var centerpos = cc.p(winsize.width / 2, winsize.height / 2);
// This line doesn't seem relevant for the question you asked.
// Or, rather, the following calculations will result in the tiles
// being centred on the screen anyway, so this calculation here
// is unnecessary.
// Being a chess board, I assume that you want the tiles to be square,
// i.e. to have the same width and height.
// If so, first find out which is the minimum dimension
// and calculate the tile size as being 1/8 of that.
var minDimn = Math.min(winsize.width, winsize.height);
var tileSize = minDimn / 8;
// Find out how far in from the left and how far down from the top
// you need the upper left corner of the upper left tile to start.
// This assumes that you don't need any "margin" around the board.
// (If you do need such a "margin", basically subtract it twice
// from each of winsize.width and winsize.height above.)
// Start with default values of 0 for each, but then add in the
// excess for the longer dimension, but divide it by two
// because that excess will be split between either
// the top and bottom or the left and right.
var xStart = 0, yStart = 0;
if (winsize.width > winsize.height) {
xStart = (winsize.width - winsize.height) / 2;
} else if (winsize.height > winsize.width) {
yStart = (winsize.height - winsize.width) / 2;
}
// Instead of looping through all 64 positions in one loop,
// loop through all the horizontal positions in an outer loop
// and all the vertical positions in an inner loop.
for (i = 0; i < 8; i++) {
// For the horizontal dimension, calculate x for each tile
// as the starting position of the left-most tile plus
// the width of the tile multiplied by the number of tiles (0-based)
var x = xStart + i * tileSize;
// Now the inner loop
for (j = 0; j < 8; j++) {
// Same type of calculation for the y value.
var y = yStart + j * tileSize;
// You can see the values in this demo here.
document.write("<pre>(" + x + ", " + y + ")</pre>");
// The following two lines don't seem to be relevant to the question.
// var tile = cc.Sprite.create(res.tile_png);
// this.addChild(tile,0);
// Use your calculated values in your function call.
// tile.setPosition(x, y);
}
}

Detect percent on screen of an element

I'm trying to detect what % of the element can be seen on the current window.
For example, if the user can only see half the element, return 50. If the user can see the whole element, return 100.
Here's my code so far:
function getPercentOnScreen() {
var $window = $(window),
viewport_top = $window.scrollTop(),
viewport_height = $window.height(),
viewport_bottom = viewport_top + viewport_height,
$elem = $(this),
top = $elem.offset().top,
height = $elem.height(),
bottom = top + height;
return (bottom - viewport_top) / height * 100;
}
But it doesn't seem to be working. Can anyone help me out in achieveing this I seem to be spinning gears.
What you want to get is the amount of pixels that the element extends past the top and bottom of the viewport. Then you can just subtract it from the total height and divide by that height to get the percentage onscreen.
var px_below = Math.max(bottom - viewport_bottom, 0);
var px_above = Math.max(viewport_top - top, 0);
var percent = (height - px_below - px_above) / height;
return percent;
One thing to note is that jQuery's height method won't include padding. You probably want to use .outerHeight for that.
Your $elem = $(this)assignment seems wrong, here function scoping means this refers to the function you're in (ala ~ the function getPercentOnScreen), try referencing by $elem = $('#yourElementId')instead.
if you only want to calculate percent of element then just do this
function getPercentOnScreen(elem) {
$docHeight = $(document).height();
$elemHeight = $(elem).height();
return ($elemHeight/$docHeight)* 100;
}

randomly mapping divs

I am creating a new "whack-a-mole" style game where the children have to hit the correct numbers in accordance to the question. So far it is going really well, I have a timer, count the right and wrong answers and when the game is started I have a number of divs called "characters" that appear in the container randomly at set times.
The problem I am having is that because it is completely random, sometimes the "characters" appear overlapped with one another. Is there a way to organize them so that they appear in set places in the container and don't overlap when they appear.
Here I have the code that maps the divs to the container..
function randomFromTo(from, to) {
return Math.floor(Math.random() * (to - from + 1) + from);
}
function scramble() {
var children = $('#container').children();
var randomId = randomFromTo(1, children.length);
moveRandom('char' + randomId);
}
function moveRandom(id) {
var cPos = $('#container').offset();
var cHeight = $('#container').height();
var cWidth = $('#container').width();
var pad = parseInt($('#container').css('padding-top').replace('px', ''));
var bHeight = $('#' + id).height();
var bWidth = $('#' + id).width();
maxY = cPos.top + cHeight - bHeight - pad;
maxX = cPos.left + cWidth - bWidth - pad;
minY = cPos.top + pad;
minX = cPos.left + pad;
newY = randomFromTo(minY, maxY);
newX = randomFromTo(minX, maxX);
$('#' + id).css({
top: newY,
left: newX
}).fadeIn(100, function () {
setTimeout(function () {
$('#' + id).fadeOut(100);
window.cont++;
}, 1000);
});
I have a fiddle if it helps.. http://jsfiddle.net/pUwKb/8/
As #aug suggests, you should know where you cannot place things at draw-time, and only place them at valid positions. The easiest way to do this is to keep currently-occupied positions handy to check them against proposed locations.
I suggest something like
// locations of current divs; elements like {x: 10, y: 40}
var boxes = [];
// p point; b box top-left corner; w and h width and height
function inside(p, w, h, b) {
return (p.x >= b.x) && (p.y >= b.y) && (p.x < b.x + w) && (p.y < b.y + h);
}
// a and b box top-left corners; w and h width and height; m is margin
function overlaps(a, b, w, h, m) {
var corners = [a, {x:a.x+w, y:a.y}, {x:a.x, y:a.y+h}, {x:a.x+w, y:a.y+h}];
var bWithMargins = {x:b.x-m, y:b.y-m};
for (var i=0; i<corners.length; i++) {
if (inside(corners[i], bWithMargins, w+2*m, h+2*m) return true;
}
return false;
}
// when placing a new piece
var box;
while (box === undefined) {
box = createRandomPosition(); // returns something like {x: 15, y: 92}
for (var i=0; i<boxes.length; i++) {
if (overlaps(box, boxes[i], boxwidth, boxheight, margin)) {
box = undefined;
break;
}
}
}
boxes.push(box);
Warning: untested code, beware the typos.
The basic idea you will have to implement is that when a random coordinate is chosen, theoretically you SHOULD know the boundaries of what is not permissible and your program should know not to choose those places (whether you find an algorithm or way of simply disregarding those ranges or your program constantly checks to make sure that the number chosen isn't within the boundary is up to you. the latter is easier to implement but is a bad way of going about it simply because you are entirely relying on chance).
Let's say for example coordinate 50, 70 is selected. If the picture is 50x50 in size, the range of what is allowed would exclude not only the dimensions of the picture, but also 50px in all directions of the picture so that no overlap may occur.
Hope this helps. If I have time, I might try to code an example but I hope this answers the conceptual aspect of the question if that is what you were having trouble with.
Oh and btw forgot to say really great job on this program. It looks awesome :)
You can approach this problem in at least two ways (these two are popped up in my head).
How about to create a 2 dimensional grid segmentation based on the number of questions, the sizes of the question panel and an array holding the position of each question coordinates and then on each time frame to position randomly these panels on one of the allowed coordinates.
Note: read this article for further information: http://eloquentjavascript.net/chapter8.html
The second approach follow the same principle, but this time to check if the panel overlap the existing panel before you place it on the canvas.
var _grids;
var GRID_SIZE = 20 //a constant holding the panel size;
function createGrids() {
_grids = new Array();
for (var i = 0; i< stage.stageWidth / GRID_SIZE; i++) {
_grids[i] = new Array();
for (var j = 0; j< stage.stageHeight / GRID_SIZE; j++) {
_grids[i][j] = new Array();
}
}
}
Then on a separate function to create the collision check. I've created a gist for collision check in Actionscript, but you can use the same principle in Javascript too. I've created this gist for inspirational purposes.
Just use a random number which is based on the width of your board and then modulo with the height...
You get a cell which is where you can put the mole.
For the positions the x and y should never change as you have 9 spots lets say where the mole could pop up.
x x x
x x x
x x x
Each cell would be sized based on % rather then pixels and would allow re sizing the screen
1%3 = 1 (x)
3%3 = 0 (y)
Then no overlap is possible.
Once the mole is positioned it can be show or hidden or moved etc based on some extended logic if required.
If want to keep things your way and you just need a quick re-position algorithm... just set the NE to the SW if the X + width >= x of the character you want to check by setting the x = y+height of the item which overlaps. You could also enforce that logic in the drawing routine by caching the last x and ensuring the random number was not < last + width of the item.
newY = randomFromTo(minY, maxY);
newX = randomFromTo(minX, maxX); if(newX > lastX + characterWidth){ /*needful*/}
There could still however be overlap...
If you wanted to totally eliminate it you would need to keep track of state such as where each x was and then iterate that list to find a new position or position them first and then all them to move about randomly without intersecting which would would be able to control with just padding from that point.
Overall I think it would be easier to just keep X starting at 0 and then and then increment until you are at a X + character width > greater then the width of the board. Then just increase Y by character height and Set X = 0 or character width or some other offset.
newX = 0; newX += characterWidth; if(newX + chracterWidth > boardWidth) newX=0; newY+= characterHeight;
That results in no overlap and having nothing to iterate or keep track of additional to what you do now, the only downside is the pattern of the displayed characters being 'checker board style' or right next to each other (with possible random spacing in between horizontal and vertical placement e.g. you could adjust the padding randomly if you wanted too)
It's the whole random thing in the first place that adds the complexity.
AND I updated your fiddle to prove I eliminated the random and stopped the overlap :)
http://jsfiddle.net/pUwKb/51/

Categories