<!-- Topbar -->
<header class="topbar">
	<ul>
		<li><h1>Low Poly Background</h1></li>
    <li><a href="https://cojdev.github.io/lowpoly" target="_top">Full Version</a></li>
		<li><a class="github-button" href="https://github.com/cojdev/lowpoly" data-icon="octicon-star" data-show-count="true" aria-label="Star cojdev/lowpoly on GitHub">Star</a></li>
	</ul>
</header>
<!-- Canvas Display -->
<section class="display">
	<div class="wrap">
		<div id="loader" class="loader">
			<div class="circle"></div>
			<div class="circle"></div>
			<div class="circle"></div>
			<div class="circle"></div>
		</div>
	<canvas id="canvas" class="canvas1"></canvas>
	</div>
	<canvas id="canvas2" class="canvas2"></canvas>
  <div class="footer">
    Developed by <a href="https://cojdev.github.io" target="_top">Charles Ojukwu</a>
  </div>
</section>
<!-- Controls -->
<section class="controls">

	<h3>Dimensions</h3>
	<div class="fieldgroup">
		<label for="preset-size">Presets</label>
		<select id="preset-size">
			<option value="1280-720">1280x720</option>
			<option value="1366-768">1366x768</option>
			<option value="1920-1080">1920x1080</option>
			<option value="3840-2160">3840x2160 (4K)</option>
			<option value="640-1152">iPhone 5/5s</option>
			<option value="750-1334">iPhone 6/7/8</option>
			<option value="1080-1920">iPhone 6+/7+/8+</option>
		</select>
		<div class="row">
			<div class="_50">
				<label for="width">Width</label>
				<input type="number" id="width">
			</div>
			<div class="_50">
				<label for="height">Height</label>
				<input type="number" id="height">
			</div>
		</div>
		<button id="size-btn">Set</button>
	</div>

	<h3>Geometry</h3>
	<div class="fieldgroup">
		<label for="variance">Variance</label>
		<input type="range" id="variance" value="40">

		<label for="cell-size">Cell Size</label>
		<input type="range" id="cell-size" value="40">

		<label for="oamount">Depth</label>
		<input type="range" id="oamount" value="10">
	</div>
	

	<h3>Colour</h3>
	<div class="fieldgroup">
		<label for="numColours">No. of colours: <span id="num-colours"></span></label>
		<input type="range" id="numColours" value="2" max="4" min="1" step="1"><br>
		<!-- Container for dynamically generated color input elememets -->
		<div id="colour-div"></div>
	</div>
	<div class="section">
		<a id="save" class="button" download="lowpoly.png">Download Image</a>
	</div>
	
</section>
<!-- Loader -->
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
	<defs>
	<filter id="goo">
		<feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur" />
		<feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 18 -7" result="goo" />
		<feBlend in="SourceGraphic" in2="goo" />
	</filter>
	</defs>
</svg>

<div id="modal">
  <div class="modal-inner">
    <button id="close-modal">&times;</button>
    <p>Click below to see the updated version</p>
    
    <a href="https://cojdev.github.io/lowpoly" class="button" target="_blank">Visit Site</a>
    
  </div>
</div>
@charset "UTF-8";
*, *:before, *:after {
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box; }

body {
  padding: 0;
  margin: 0;
font-size: 16px;}

ul, li {
  list-style: none;
  margin: 0;
  padding: 0; }

button {
  outline: none;
  cursor: pointer;
  border: none; }

*, *::before, *::after {
  box-sizing: border-box; }

html,
body {
  margin: 0;
  padding: 0; }

html {
  font-family: 'Barlow', sans-serif;
  color: #bbb;
  background: #ddd;
  font-size: 16px; }

a {
  color: #5ad;
  text-decoration: none; }
  a:hover {
    text-decoration: underline; }

.topbar {
  height: 40px;
  background: #333; }
  .topbar li {
    display: inline; }
  .topbar li:not(:last-child)::after {
    content: " •";
    color: #666;
    margin: 0 .25rem; }
  .topbar h1 {
    display: inline;
    font-size: 1rem;
    font-weight: 400;
    color: #f8f8f8;
    margin: 0;
    line-height: 40px;
    padding-left: 1rem; }

.display {
  width: calc(100% - 200px);
  text-align: center;
  bottom: 0;
  position: fixed;
  left: 0;
  top: 40px;
  bottom: 0; }

.row {
  margin: 0 -0.5rem; }
  .row:before, .row:after {
    content: '';
    display: table; }
  .row:after {
    clear: both; }

._50 {
  float: left;
  padding: 0 0.5rem;
  width: 50%; }

.wrap {
  position: relative;
  display: inline-block;
  height: calc(100vh - 40px);
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center; }

.canvas1 {
  max-width: 95%;
  max-height: 95%;
  box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1); }

.canvas2 {
  display: none; }

button,
.button {
  display: inline-block;
  text-decoration: none;
  background: linear-gradient(to bottom right, #D84155, #882963);
  padding: 0.6rem;
  color: #fff;
  border-radius: 2px;
  width: 100%; }
  button:hover,
  .button:hover {
    background: linear-gradient(to bottom right, #e16b7b, #af3580); }

.section {
  padding: 1rem; }

input[type=text],
input[type=number],
input[type=range],
select {
  display: block;
  width: 100%; }

input[type=text],
input[type=number],
select {
  height: 2rem;
  line-height: 2rem;
  padding: 0 .5rem;
  background: #fff;
  border: none;
  margin-bottom: .75rem; }

.controls {
  position: fixed;
  top: 40px;
  right: 0;
  bottom: 0;
  width: 200px;
  background: #333;
  padding: 0;
  box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
  overflow-y: auto;
  overflow-x: hidden;
  border-top: 1px solid #444; }
  .controls h3 {
    color: #f8f8f8;
    padding: .5rem 1rem;
    margin: 0;
    font-weight: 600; }

.fieldgroup {
  padding: 1rem;
  background: #222; }

label {
  display: block; }

.footer {
  position: absolute;
  color: #888;
  bottom: 2rem;
  width: 100%;
  font-size: .9em;
  text-align: center;
}

.loader {
  position: absolute;
  top: 50%; }

.loader {
  position: absolute;
  filter: url("#goo");
  top: calc(50% - 15px);
  left: calc(50% - 45px);
  height: 30px;
  width: 90px; }

.circle {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: #fff;
  position: absolute;
  left: 30px; }
  .circle:first-child {
    animation: a1 1200ms ease-out infinite; }
  .circle:last-child {
    animation: a2 1200ms ease-out infinite; }
  .circle:nth-child(2) {
    animation: a3 1200ms ease-out infinite; }
  .circle:nth-child(3) {
    animation: a4 1200ms ease-out infinite; }

#modal {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: rgba(0,0,0, 0.2);
  transition: 300ms ease;
}

.modal--hide {
  opacity: 0;
  visibility: hidden;
}

.modal--hide .modal-inner {
  transform: scale(.9);
  opacity: 0;
}

.modal-inner {
  width: 500px;
  max-width: 100%;
  background-color: #333;
  text-align: center;
  position: relative;
  padding: 2rem 1rem;
  border-radius: 5px;
  box-shadow: 0 2px 5px rgba(0,0,0,0.2);
  transition: 300ms ease;
}

#close-modal {
  width: 2rem;
  height: 2rem;
  position: absolute;
  top: 1rem;
  right: 1rem;
}

@keyframes a1 {
  0% {
    transform: translateX(0); }
  6% {
    transform: translateX(-3.75px) scale(0.75); }
  24%, 30% {
    transform: translateX(-15px) scale(0.65); }
  36% {
    transform: translateX(-15px) scale(0.5); }
  60%, 70% {
    transform: translateX(-15px) scale(0.4); }
  85% {
    transform: translateX(-3.75px) scale(0.9); }
  90% {
    transform: translateX(0); } }

@keyframes a2 {
  0% {
    transform: translateX(0); }
  6% {
    transform: translateX(-3.75px) scale(0.75); }
  24%, 30% {
    transform: translateX(-15px) scale(0.65); }
  36% {
    transform: translateX(-22.5px) scale(0.5); }
  60%, 70% {
    transform: translateX(-45px) scale(0.4); }
  85% {
    transform: translateX(-3.75px) scale(0.9); }
  90% {
    transform: translateX(0); } }

@keyframes a3 {
  0% {
    transform: translateX(0); }
  6% {
    transform: translateX(3.75px) scale(0.75); }
  24%, 30% {
    transform: translateX(15px) scale(0.65); }
  36% {
    transform: translateX(22.5px) scale(0.5); }
  60%, 70% {
    transform: translateX(45px) scale(0.4); }
  85% {
    transform: translateX(3.75px) scale(0.9); }
  90% {
    transform: translateX(0); } }

@keyframes a4 {
  0% {
    transform: translateX(0); }
  6% {
    transform: translateX(3.75px) scale(0.75); }
  24%, 30% {
    transform: translateX(15px) scale(0.65); }
  36% {
    transform: translateX(15px) scale(0.5); }
  60%, 70% {
    transform: translateX(15px) scale(0.4); }
  85% {
    transform: translateX(3.75px) scale(0.9); }
  90% {
    transform: translateX(0); }
}
/**
 * Author: Charles Ojukwu
 */
(function () {

	'use strict';

	var cvs,//canvas
		ctx,//canvas context
		gridWidth,//draw width (2 cells wider than the actual canvas)
		gridHeight,//draw height (2 cells taller than the actual canvas)
		vRange,
		cRange,
		maxCols,
		maxRows,
		oAmount,
		imgd, imge,
		base_image,
		saveBtn,
		cvs2,
		ctx2,
		loader,
		numColours,
		preset;
	var points = [];
	var cellSize = 50;//size of a single grid square
	var variance = 0.2;
	var ovA = 0.5;
	var colours = ["#22bbee", "#8855cc", "#ee2266", "#ee7722"];

	function init() {
		//Add on load scripts
		cvs = document.getElementById("canvas");
		cRange = document.getElementById("cell-size");
		vRange = document.getElementById("variance");
		oAmount = document.getElementById("oamount");
		saveBtn = document.getElementById('save');
		ctx = cvs.getContext("2d");
		cvs2 = document.getElementById("canvas2");
		ctx2 = cvs2.getContext("2d");
		numColours = document.getElementById("numColours");

		preset = document.getElementById("preset-size");

		preset.addEventListener("change", function () {
			var temp5 = preset.value.split("-");

			document.getElementById('width').value = parseInt(temp5[0]);
			document.getElementById('height').value = parseInt(temp5[1]);
		}, false);


		var setSize = function (w, h) {
			cvs.width = w;
			cvs.height = h;

			gridWidth = cvs.width + cellSize * 2;
			gridHeight = cvs.height + cellSize * 2;

			cvs2.width = cvs.width;
			cvs2.height = cvs.height;

			//Repopulate inputs
			document.getElementById('width').value = w;
			document.getElementById('height').value = h;
		}

		var setCols = function () {
			for (var i = 0; i < numColours.value; i++) {
				var temp = document.createElement("input");
				temp.type = "color";
				temp.id = "colour" + (i + 1);

				document.getElementById("colour-div").appendChild(temp);
				document.getElementById("num-colours").innerHTML = numColours.value;
				temp.value = colours[i];
				temp.addEventListener("change", function () {
					drawBG(ctx2, cvs2);
					loader.style.display = "";

					var blob = window.setTimeout(function () {
						pointFun();
					}, 2);
				}, false);
			}
		}

		setCols();

		setSize(1280, 720);
		drawBG(ctx2, cvs2);
		loader = document.getElementById("loader");
		pointFun();
		
		var tout = 0;

		numColours.addEventListener("change", function () {
			document.getElementById("colour-div").innerHTML = "";
			setCols();
			drawBG(ctx2, cvs2);
			loader.style.display = "";
			var blob = window.setTimeout(function () {
				pointFun();
			}, tout);
		}, false);
		cRange.addEventListener("change", function () {
			loader.style.display = "";

			var blob = window.setTimeout(function () {
				pointFun();
			}, tout);
		}, false);
		vRange.addEventListener("change", function () {
			loader.style.display = "";

			var blob = window.setTimeout(function () {
				pointFun();
			}, tout);
		}, false);
		oAmount.addEventListener("change", function () {
			loader.style.display = "";

			var blob = window.setTimeout(function () {
				pointFun(true);
			}, tout);
		}, false);

		var sizeBtn = document.getElementById('size-btn');
		sizeBtn.addEventListener("click", function () {
			var tempw = document.getElementById('width').value;
			var temph = document.getElementById('height').value;
			setSize(tempw, temph);
			drawBG(ctx2, cvs2);
			pointFun();
		});

		var modal = document.getElementById('modal');
		var closeModal = document.getElementById('close-modal');

		closeModal.addEventListener('click', function() {
			modal.classList.add('modal--hide');
		});
	}

	function drawBG(context, canvas) {
		context.clearRect(0, 0, cvs.width, cvs.height);
		context.globalCompositeOperation = "multiply";
		var bg = context.createLinearGradient(0, 0, canvas.width, 0);
		var inputs = document.getElementById("colour-div").getElementsByTagName('input');
		for (var i = 0; i < inputs.length; i++) {
			if (inputs.length > 1) {
				bg.addColorStop(i / (inputs.length - 1), inputs[i].value);
			} else {
				bg = inputs[i].value;
			}
			colours[i] = inputs[i].value;
		}

		context.fillStyle = bg;
		context.beginPath();
		context.fillRect(0, 0, canvas.width, canvas.height);
		context.closePath();
		context.fill();

		context.beginPath();
		var bg2 = context.createLinearGradient(0, 0, 0, cvs.height);
		bg2.addColorStop(0, "#fff");
		bg2.addColorStop(1, "#ccc");
		context.fillStyle = bg2;
		context.fillRect(0, 0, cvs.width, cvs.height);
		context.closePath();
		context.fill();
		context.globalCompositeOperation = "source-over";
		if (context == ctx2) { imgd = context; }
		else if (imgd.getImageData(0, 0, 1, 1).data[3] < 255 || imgd.getImageData(canvas.width - 1, canvas.height - 1, 1, 1).data[3] < 255) {
			context.clearRect(0, 0, canvas.width, canvas.height);
		}


	}

	function saveImage() {
		var dataURL = canvas.toDataURL();
		saveBtn.href = dataURL;
	}

	function draw(obj, rand) {

		var red, blue, green, alpha;

		var setColour = function (xpos, ypos) {
			if (xpos < 0) {
				xpos = 0;
			}
			if (xpos > cvs.width - 1) {
				xpos = cvs.width - 1;
			}
			if (ypos < 0) {
				ypos = 0;
			}
			if (ypos > cvs.height - 1) {
				ypos = cvs.height - 1;
			}
			red = imgd.getImageData(xpos, ypos, 1, 1).data[0];
			green = imgd.getImageData(xpos, ypos, 1, 1).data[1];
			blue = imgd.getImageData(xpos, ypos, 1, 1).data[2];
			alpha = imgd.getImageData(xpos, ypos, 1, 1).data[3];

		}

		var filler = function () {
			if (rand) {
				var temp = (Math.random() * 2 * ovA) - ovA;
				ctx.fillStyle = "rgba(" + Math.round(red - red * temp) + ", " + Math.round(green - green * temp) + ", " + Math.round(blue - blue * temp) + ", " + alpha / 255 + ")";
			} else {
				ctx.fillStyle = "rgba(" + red + ", " + green + ", " + blue + ", " + alpha / 255 + ")";
			}
		}

		for (var i = 0; i < points.length; i++) {
			if (obj == points[i] && obj.r % 2 == 0 && points[i + maxCols + 1] && obj.c < maxCols - 1) {
				
				setColour(Math.round(obj.c / maxCols * gridWidth), Math.round(obj.r / maxRows * gridHeight + 2 * cellSize / 3));
				filler();
				
				ctx.beginPath();
				ctx.moveTo(obj.x, obj.y);
				ctx.lineTo(points[i + maxCols].x, points[i + maxCols].y);
				ctx.lineTo(points[i + maxCols + 1].x, points[i + maxCols + 1].y);
				ctx.closePath();
				ctx.fill();

				setColour(Math.round(obj.c / maxCols * gridWidth + cellSize / 2), Math.round(obj.r / maxRows * gridHeight + cellSize / 3));
				filler();
				
				ctx.beginPath();
				ctx.moveTo(obj.x, obj.y);
				ctx.lineTo(points[i + 1].x, points[i + 1].y);
				ctx.lineTo(points[i + maxCols + 1].x, points[i + maxCols + 1].y);
				ctx.closePath();
				ctx.fill();
			}
			if (obj == points[i] && obj.r % 2 != 0 && points[i + maxCols + 1] && obj.c > 0) {

				setColour(Math.round((obj.c - 1) / maxCols * gridWidth), Math.round(obj.r / maxRows * gridHeight + cellSize / 3));
				
				filler();
				ctx.beginPath();
				ctx.moveTo(obj.x, obj.y);
				ctx.lineTo(points[i - 1].x, points[i - 1].y);
				ctx.lineTo(points[i + maxCols - 1].x, points[i + maxCols - 1].y);
				ctx.closePath();
				ctx.fill();
				
				setColour(Math.round((obj.c - 1) / maxCols * gridWidth + cellSize / 2), Math.round(obj.r / maxRows * gridHeight + 2 * cellSize / 3));
				filler();
				
				ctx.beginPath();
				ctx.moveTo(obj.x, obj.y);
				ctx.lineTo(points[i + maxCols - 1].x, points[i + maxCols - 1].y);
				ctx.lineTo(points[i + maxCols].x, points[i + maxCols].y);
				ctx.closePath();
				ctx.fill();
				
			}
		}
	}

	//Point generator
	function generatePoints(amount) {
		points = [];
		var temp;
		var row = 0;
		var col = 0;
		for (var i = 0; i < amount; i++) {
			temp = {};
			if (row % 2 == 0) {
				temp.x = (col * cellSize) - cellSize;
				temp.x = temp.x + (Math.random() - .5) * variance * cellSize * 2;
			} else {
				temp.x = (col * cellSize) - cellSize - cellSize / 2;
				temp.x = temp.x + (Math.random() - .5) * variance * cellSize * 2;
			}
			temp.y = (row * cellSize * 0.866) - cellSize;
			temp.y = temp.y + (Math.random() - .5) * variance * cellSize * 2;
			temp.r = row;
			temp.c = col;
			points.push(temp);
			col = col + 1;
			if ((i + 1) % maxCols == 0) {
				row = row + 1;
				col = 0;
			}
		}
	}

	function pointFun(drawOnly) {
		if (!drawOnly) {
			cellSize = (cRange.value * 3) + 30;
			variance = vRange.value / 100;
			gridWidth = cvs.width + cellSize * 2;
			gridHeight = cvs.height + cellSize * 2;
			maxCols = Math.ceil(gridWidth / cellSize) + 2;
			maxRows = Math.ceil(gridHeight / (cellSize * 0.865))
			var x = maxCols;
			var y = maxRows;
			generatePoints(x * y);
		}
		ovA = oAmount.value / 100;
		ctx.clearRect(0, 0, cvs.width, cvs.height);
		drawBG(ctx, cvs);
		for (var i = 0; i < points.length; i++) {
			draw(points[i], true);
		};
		saveImage();
		loader.style.display = "none";
	}

	//Execute when DOM has loaded
	document.addEventListener('DOMContentLoaded', init, false);

})();

External CSS

  1. https://fonts.googleapis.com/css?family=Barlow:300,400,600

External JavaScript

  1. https://buttons.github.io/buttons.js