2
\$\begingroup\$

I made a header generator that creates a linear gradient of two random colors from its top left to its bottom right.

Please tell me about everything I could do better, but especially how to calculate and not hardcode the randomly shifted second array secondColor, that has the minColorValue, maxColorValue and the random value on other positions.

const settings = {
  width: 500,
  height: 180,
  minColorValue: 70, // min rgb value
  maxColorValue: 185, // max rgb value
};
let container = document.getElementsByClassName("container")[0];
let firstColor = [0, 0, 0];
let secondColor = [0, 0, 0];

let create = function(width, height) {
  let canvas = document.createElement("canvas");
  canvas.width = width;
  canvas.height = height;
  container.appendChild(canvas);
};

let colorPicker = function() {
  // possible configurations of which of the rgb values is min, which is max, and which is inbetween
  let configurations = [
    [0, 1, 2],
    [0, 2, 1],
    [1, 0, 2],
    [1, 2, 0],
    [2, 0, 1],
    [2, 1, 0],
  ];
  let randomConfig = configurations[random(0, 5)];
  firstColor[randomConfig[0]] = settings.minColorValue;
  firstColor[randomConfig[1]] = settings.maxColorValue;
  firstColor[randomConfig[2]] = random(settings.minColorValue, settings.maxColorValue);

  // shift configuration by one or two to never get a different second color
  let shiftAmount = random(1, 2);
  let secondConfig = [0, 0, 0];
  for (let i = 0; i < 3; i++) {

    if (i + shiftAmount < 3) {
      secondConfig[i] = randomConfig[i + shiftAmount];
    } else {
      secondConfig[i] = randomConfig[i - 3 + shiftAmount];
    }
  }
  secondColor[secondConfig[0]] = settings.minColorValue;
  secondColor[secondConfig[1]] = settings.maxColorValue;
  secondColor[secondConfig[2]] = random(settings.minColorValue, settings.maxColorValue);

};

let fill = function() {
  let canvas = document.getElementsByTagName("canvas")[0];
  let context = canvas.getContext("2d");
  let gradient = context.createLinearGradient(0, 0, settings.width, settings.height);
  gradient.addColorStop(0, `rgb(${firstColor[0]},${firstColor[1]},${firstColor[2]})`);
  gradient.addColorStop(1, `rgb(${secondColor[0]},${secondColor[1]},${secondColor[2]})`);
  context.fillStyle = gradient;
  context.fillRect(0, 0, settings.width, settings.height);
};
let start = function() {
  create(settings.width, settings.height);
  colorPicker();
  fill();
};

// help function
function random(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
html {
  background-color: #1d1f21;
}

canvas {
  display: inline-block;
}

.container {
  text-align: center;
}

.textbox {
  text-align: center;
  color: white;
  font-family: monospace;
}
<head>
  <title>random gradient header</title>
  <meta charset="UTF-8">
  <link href="style.css" rel="stylesheet">
</head>

<body onload="start()">
  <div class="container"></div>
  <div class="textbox">Reload to get a different header gradient.</div>
  <script src="short.js"></script>
</body>

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Please tell me about everything I could do better, but especially how to calculate and not hardcode the randomly shifted second array [...].

I suggest to get a random permutation of [0, 1, 2] by introducing a simple but unbiased shuffle function.

A circular shift of that random permutation can then be found by mapping index i to index (i + shift) % length:

// Return two shifted colors with min, max and random component:
function randomGradientColors(min, max) {
  let permutation = shuffle([0, 1, 2]);
  let shift = random(1, 2);
  return [
    permutation.map(i => [min, max, random(min, max)][i]),
    permutation.map(i => [min, max, random(min, max)][(i + shift) % 3])
  ];
}

Since you asked for everything that you could do better, I would like to suggest using a CSS background gradient instead of a canvas element. If compatibility is not a concern, you can even leverage fancy CSS variables:

.gradient {
  background: linear-gradient(90deg, var(--from, black), var(--to, black));
}

Your markup then would provide the min and max color components via data attributes:

<div class="gradient" data-min="70" data-max="185"></div>

And your script would simply query all .gradient elements and compute their CSS from and to variables from the provided data attributes:

let gradients = document.querySelectorAll(".gradient");
for (let gradient of gradients) {
  let [from, to] = randomGradientColors(
    gradient.dataset.min,
    gradient.dataset.max
  );
  gradient.style.setProperty("--start", "rgb(" + from + ")");
  gradient.style.setProperty("--to", "rgb(" + to + ")");
}

This approach is pretty flexible and allows you to adapt the styling to your needs without touching the script.

Here is the complete code:

// Return random integer within interval [min, max]:
function random(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

// Shuffle array in-place:
function shuffle(array) {
  for (let i = array.length - 1; i > 0; i--) {
    let j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
  return array;
}

// Return two shifted colors with min, max and random component:
function randomGradientColors(min, max) {
  let permutation = shuffle([0, 1, 2]);
  let shift = random(1, 2);
  return [
    permutation.map(i => [min, max, random(min, max)][i]),
    permutation.map(i => [min, max, random(min, max)][(i + shift) % 3])
  ];
}

// Compute CSS 'from' and 'to' variables for all gradients:
let gradients = document.querySelectorAll(".gradient");
for (let gradient of gradients) {
  let [from, to] = randomGradientColors(
    gradient.dataset.min,
    gradient.dataset.max
  );
  gradient.style.setProperty("--from", "rgb(" + from + ")");
  gradient.style.setProperty("--to", "rgb(" + to + ")");
}
html {
  background-color: #1d1f21;
}

.gradient {
  display: inline-block;
  width: 500px;
  height: 180px;
  background: linear-gradient(90deg, var(--from, black), var(--to, black));
}

.container {
  text-align: center;
}

.textbox {
  text-align: center;
  color: white;
  font-family: monospace;
}
<div class="container">
  <div class="gradient" data-min="70" data-max="185"></div>
</div>
<div class="textbox">Reload to get a different header gradient.</div>

If you wish to support older browsers, remove the CSS variables and use

gradient.style.background = 
  "linear-gradient(90deg, rgb(" + from + "), rgb(" + to + "))";
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.