Constrained random numbers always total the constraint with javascript/jquery - javascript

I'm a JS amateur. I'm looking to randomly set the width and opacity of a bunch of span elements to create an animation effect.
At the moment, the width is set and re-set using setInterval every second which is almost working fine...
$(function () {
setInterval(function () {
// Variables for background colour
var minFloat = 0.3,
maxFloat = 0.9,
randFloat = Math.floor(Math.random() * (maxFloat - minFloat + 1)) + minFloat;
// Set random width
$('.footerbars span').css('width', Math.random() * 10 + '%');
// Set random alpha
$('.footerbars span').css('background-color', 'rgba(58,130,255,' + randFloat + ')');
}, 1000);
});
What I need is:
the widths of the spans to be each be a different percentage, and for all of those percentages to always total 100%.
and the background alpha channel to be random for each span
Any help is awesome!! Thanks in advance

The first problem is that all widths and backgrounds will be set to be the same as the random numbers are only generated once. You need something like:
$('.footerbars span').each(function(i, e) {
$(e).css('width', (Math.random() * 10) + '%')
.css('background-color', 'rgba('58,130,255,' + ((Math.random() * 0.6) + 0.3) +')');
});
The problem with this is that the widths may not all add up to 100%. To solve this we need to first generate the set of random numbers, then scale them so they add up to 100, and then apply them to the spans.
var numSpans = $('.footerbars span').length;
var widths = [];
var total = 0;
for(var i = 0; i < numSpans; i++) {
widths[i] = Math.random()+1; // generate a new random width for this span - and make it definitely not zero
total += widths[i]; // and update the total width so far;
}
// Now we know what the total random number is (something between numSpans and 2*numSpans)
// we can scale these so the sum actually is 100
for(var i = 0; i < numSpans; i++)
widths[i] = Math.floor(widths[i] * (100 / total));
Now widths[i] contains the % width of the ith span in .footerbars, so amend the second line of the first bit of code to be:
$(e).css('width', widths[i])
Full code:
var numSpans = $('.footerbars span').length;
var widths = [];
var total = 0;
for(var i = 0; i < numSpans; i++) {
widths[i] = Math.random()+1; // generate a new random width for this span - and make it definitely not zero
total += widths[i]; // and update the total width so far;
}
// Now we know what the total random number is (something between numSpans and 2*numSpans)
// we can scale these so the sum actually is 100
for(var i = 0; i < numSpans; i++)
widths[i] = Math.floor(widths[i] * (100 / total));
$('.footerbars span').each(function(i, e) {
$(e).css('width', widths[i])
.css('background-color', 'rgba('58,130,255,' + ((Math.random() * 0.6) + 0.3) +')');
});

Related

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();

Can anyone help me make my JavaScript faster?

In my program, I am making divs boxes with JavaScript and it seems to be taking quite a bit of time making a 50X50 grid.It even takes some time to make 20x20 grid. I have looked up how to make my code faster , but none of the suggestions have made an insignificance difference.
This an Odin Project
https://jsfiddle.net/Mulk/yc5rsf1m/#&togetherjs=dVAh1FK7On
$(document).ready(function(){
// Defalut Grid is 16x 16 Grid
CreateBox(16);
CallMeMaybe();
$("#gridbtn").click(function(){
$(".odd").remove();
var numbox =parseInt(prompt("How many boxes would like?"));
CreateBox(numbox);
});
function CreateBox(a) {
var wh = (500/a), i , j ;
for(i=0;i<a;i++){
for(j=0;j<a;j++){
$div = $('<div/>').appendTo('#container').addClass(".odd").attr('class','odd').width(wh).height(wh);
CallMeMaybe();
}}
};
// Play with me
function CallMeMaybe(a){
$(".odd").hover(function(){
var r = Math.floor(Math.random() * (256 - 0) + 0);
var g = Math.floor(Math.random() * (256 - 0) + 0);
var b = Math.floor(Math.random() * (256 - 0) + 0);
var color = "rgb("+r+","+g+","+b+")"
$(this).css("background-color", color);
});
};
// Play with me
});
Instead of appending each new element to the DOM, which is very expensive, append all the elements to a document fragment first, then append the whole thing to the DOM.
This should be faster. It creates an array of the divs and appends them to the DOM all at once. The divs are created with the attributes instead of changing their attributes after they have been added to the DOM. It also attaches the hover handler at the time of creation instead of attaching it after all the boxes have been created. This should significantly reduce the number of DOM manipulations that need to happen.
$(function(){
function createBox(boxesInRow) {
var wh = (500/boxesInRow), i , j, divs = [];
for(i = 0; i < boxesInRow; i++){
for(j = 0; j < boxesInRow; j++){
divs.push($('<div/>', {
"class":'odd',
height: wh + 'px',
width: wh + 'px',
hover: hoverCallback
}));
}
}
$('#container').empty().append(divs);
}
function hoverCallback() {
var r = Math.floor(Math.random() * 256),
g = Math.floor(Math.random() * 256),
b = Math.floor(Math.random() * 256);
$(this).css("background-color", "rgb("+r+","+g+","+b+")");
}
// Default Grid is 16x16 Grid
createBox(16);
$("#gridbtn").click(function() {
var numbox = parseInt(prompt("How many boxes would you like per row?"));
createBox(numbox);
});
});

How to visualise a range of values logarithmically?

I am visualising bubbles for cities, bigger if the city has a bigger value. Eg:
London: 14500
New York: 100
Tokyo: 1100
The values range from ~100-15000
I am having trouble creating a function that will return reasonable values, so that the 100 value bubbles aren't too small. The only way I could think to do this is to set a minimum size, eg:
if (size < 5) { size = 5 }
However, this causes the cities with values of ~100 to look very similar to cities with values of ~1000. I'd like the values of approx 0-15000 to return as values between 0.5 and 1 (or something similar). How would this be done?
Here's what I have so far, but like I said it's causing values of 100 and values of 1000 to both be under the min value:
var minBulletSize = 7.5;
var maxBulletSize = 20;
var maxSquare = maxBulletSize * maxBulletSize * 2 * Math.PI;
var minSquare = minBulletSize * minBulletSize * 2 * Math.PI;
// create circle for each location
for (var i = 0; i < mapData.length; i++) {
var dataItem = mapData[i];
var value = dataItem.value;
// calculate size of a bubble
var square = (value/1000 - minBulletSize) / (maxBulletSize - minBulletSize) * (maxSquare - minSquare) + minSquare;
if (square < minSquare) {
square = minSquare;
}
if (square > maxSquare) {
square = maxSquare;
}
var size = Math.sqrt(square / (Math.PI * 2));
var id = dataItem.code;
}
I have taken a look at how to make a logarithmic function to look "logarithmic" within the constraints of 0.5 and 1 :
Math.log10(x / 0.8 + 1) / 3 + 0.5 where x is in between 0 to 24.5.
This is purely a function that seems to look good for me where you can get very dynamic numbers early although a clear growth can be seen in larger numbers.
Feel free to mess around with the numbers, this is VERY subjective.
Next you will need to fit in your 100~15000 range within 0 to 24.5.
I would simply do a x = (x - 100) / 608.16 to get your range to be within 0 to 24.5.
var minBulletSize = 7.5;
var maxBulletSize = 20;
var maxSquare = maxBulletSize * maxBulletSize * 2 * Math.PI;
var minSquare = minBulletSize * minBulletSize * 2 * Math.PI;
// create circle for each location
for (var i = 0; i < mapData.length; i++) {
var dataItem = mapData[i];
var value = dataItem.value;
// assuming value is in between 100 and 15000
value = (value - 100) / 608.16;
value = Math.log10(value / 0.8 + 1) / 3.0 + 0.5;
// value will be in between 0.5 and 1 on a logarithmic scale.
// Do what you want with it :)
}
Tinker the values within the functions until you find a perfect curve for you.

Animating array of divs; only the final element is modified

This is baffling me slightly at the moment.
I'm a fairly experienced programmer who is looking to throw a bit of a surprise for my fiancee on the countdown page for our wedding. The desired effect essentially simulated confetti falling behind the main page. My setup is as follows:
I have an array of divs in javascript. Each div is absolutely positioned on the page. I have an interval set that updates every 50 or so milliseconds. On each tick, I run a for loop over my array of divs, and do calculations and update their positions.
Problem
The problem I am having, however, is that even though I can clearly see that all divs are created, stored into the array, and modified (in this case, each has a slight randomized rotation) during the initialization phase, once the tick to update their position starts, only the div stored in whatever the last slot of the array is gets its position updated. I even tried hard-coding each div by index for testing purposes, but it is strictly the last element that will actually update its position. Even upon printing the current "left" and "top" style values for each div before and after the calculations shows that the value has been changed, but no visible change is noticeable. Here's my basic code:
Javascript
var container; // the div that will have all the other divs attached to it
var conf = new Array(); // the array of divs to modify
var rots = new Array(); // the rotation of each div
var vels = new Array(); // the velocity of each div
var xPos = new Array(); // the current x value of position in pixels
var yPos = new Array(); // the current y value of position in pixels
var xVels = new Array(); // the x portion of that velocity
var yVels = new Array(); // the y portion of that velocity
function initialize() // this is attached to the onload event on my <body> tag
{
container = document.getElementById("conf_container");
confettiInit(); // creates, initializes, and displays each div
setInterval(updateConfetti, 42);
}
function confettiInit()
{
screenHeight = window.innerHeight;
screenWidth = window.innerWidth;
for (var i = 0; i < 25; i ++) // create 25 confetti flakes
{
// append a div in the container with a unique ID
container.innerHTML += "<div class='conf' id='conf_" + i + "'></div>";
// store the element in an array
conf[i] = document.getElementById("conf_" + i);
// ensure confetti is in the background, and each z index is unique
conf[i].style.zIndex = -i - 1;
xPos[i] = window.innerWidth * Math.random(); // calculate random x position
conf[i].style.left = xPos[i] + "px"; // set x position of div
yPos[i] = -40; // y position above screen
conf[i].style.top = yPos[i] + "px"; // set y position of div
// calculate the random amount of rotation (-30 to 30 degrees)
rots[i] = Math.random() * 60*(Math.PI/180) - 30*(Math.PI/180);
// set rotation of div
conf[i].style.webkitTransform = "rotate(" + -rots[i] + "rad)";
vels[i] = Math.random() * 3 + 2; // calculate random velocity (2-5)
xVels[i] = vels[i] * Math.sin(rots[i]); // get x portion of velocity
yVels[i] = vels[i] * Math.cos(rots[i]); // get y portion of velocity
}
}
function updateConfetti()
{
for (var i = 0; i < 25; i ++)
{
xPos[i] += xVels[i]; // calculate new x position
yPos[i] += yVels[i]; // calculate new y position
conf[i].style.left = xPos[i] + "px"; // set div's x position
conf[i].style.top = yPos[i] + "px"; // set div's y position
// if the confetti piece leaves the viewing area...
if (xPos[i] < -50 ||
xPos[i] > window.screenWidth + 10 ||
yPos[i] > window.screenHeight + 10)
{
// send it back to the top and reinitialize it
xPos[i] = window.innerWidth * Math.random();
conf[i].style.left = xPos[i] + "px";
yPos[i] = -40;
conf[i].style.top = yPos[i] + "px";
rots[i] = Math.random() * 60*(Math.PI/180) - 30*(Math.PI/180);
conf[i].style.webkitTransform = "rotate(" + -rots[i] + "rad)";
vels[i] = Math.random() * 3 + 2;
xVels[i] = vels[i] * Math.sin(rots[i]);
yVels[i] = vels[i] * Math.cos(rots[i]);
}
}
}
CSS
div.conf
{
height: 29px;
width: 50px;
background-image: url("images/ConfettiYellow.gif");
position: absolute;
}
#conf_container
{
left: 0px;
top: 0px;
width: 100%;
height: 100%;
position: absolute;
overflow: hidden;
}
It's something wrong with your divs creation. It start works if you do it in conventional way, like this:
var d = document.createElement("div");
d.className = "conf";
container.appendChild(d);
conf[i] = d;

convert list of relative widths to pixel widths

This is a code review question more then anything.
I have the following problem:
Given a list of relative widths (no unit whatsoever, just all relative to each other), generate a list of pixel widths so that these pixel widths have the same proportions as the original list.
input: list of proportions, total pixel width.
output: list of pixel widths, where each width is an int, and the sum of these equals the total width.
Code:
var sizes = "1,2,3,5,7,10".split(","); //initial proportions
var totalWidth = 1024; // total pixel width
var sizesTotal = 0;
for (var i = 0; i < sizes.length; i++) {
sizesTotal += parseInt(sizes[i], 10);
}
if(sizesTotal != 100){
var totalLeft = 100;;
for (var i = 0; i < sizes.length; i++) {
sizes[i] = Math.floor(parseInt(sizes[i], 10) / sizesTotal * 100);
totalLeft -= sizes[i];
}
sizes[sizes.lengh - 1] = totalLeft;
}
totalLeft = totalWidth;
for (var i = 0; i < sizes.length; i++) {
widths[i] = Math.floor(totalWidth / 100 * sizes[i])
totalLeft -= widths[i];
}
widths[sizes.lenght - 1] = totalLeft;
//return widths which contains a list of INT pixel sizes
Might be worth abstracting it to a function... I cleaned it up a bit. And I wasn't sure what the sizesTotal != 100... stuff was all about so I life it out.
function pixelWidths(proportions, totalPx) {
var pLen = proportions.length,
pTotal = 0,
ratio, i;
for ( i = -1; ++i < pLen; )
pTotal += proportions[i];
ratio = totalPx / pTotal;
pTotal = 0;
for ( i = -1; ++i < pLen; )
pTotal += proportions[i] = ~~(proportions[i] * ratio);
proportions[pLen-1] += totalPx - pTotal;
return proportions;
}
pixelWidths([1,2,3,5,7,10], 1024); // => [36, 73, 109, 182, 256, 368]
FYI, ~~ (double-bitwise-not) has the effect of getting the number representation of any type (using the internal toInt32 operation) and then flooring it. E.g:
~~'2'; // => 2
~~'2.333'; // => 2
~~null; // => 0
If sizes starts off declared as a list of numbers, why do you have to call parseInt()?
You misspelled "length" in the last line
Where is widths declared?
How does this account for rounding issues? Oh I see; it's that last line; well don't you need to add totalLeft and not just override whatever's there?

Categories