<html>
<head>
<style>
body {
margin: 0;
background-color: hsl(0, 0%, 10%);
}
canvas {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: -1;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<script>
"use strict";
var options = {
loopCount: 9,
loopWidth: 0.125,
loopSpace: 0,
loopSpeed: 2,
lineWidth: 10,
variation: 5,
friction: 25,
lines: 4,
innerSmall: true,
sway: true,
};
var parameters = {
x: 0,
y: 0,
radius: 0
};
function loop(func) {
function frame(timestamp) {
if (func(timestamp))
window.requestAnimationFrame(frame);
}
window.requestAnimationFrame(frame);
}
function map(num, in_min, in_max, out_min, out_max) {
return (num - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
var canvas = document.body.appendChild(document.createElement("canvas"));
function handleResize() {
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
parameters.x = canvas.width / 2;
parameters.y = canvas.height / 2;
parameters.radius = Math.min(parameters.x, parameters.y) * 4 / 5;
}
handleResize();
window.addEventListener("resize", handleResize);
var context = canvas.getContext("2d");
var start = (new Date()).getTime();
function drawCircle(i, progress, offset, solid) {
var now = (new Date).getTime() - start;
var stage = (solid ? Math.PI * 2 : i * 2) - i;
var os = options.loopWidth * Math.max(0, Math.min((now - (stage * 2500)) / 2500, Math.pow(map(Math.cos(i - (Math.PI * 2 * progress)), -1, 1, 0, 1), options.loopSpace)));
var r = parameters.radius * (1 + (os * Math.cos((options.loopCount * i) + (options.loopSpeed * Math.PI * 2 * progress) + offset)));
var c = {
x: parameters.x + (r * Math.sin(i)),
y: parameters.y - (r * Math.cos(i))
};
var min = parameters.radius * (1 - options.loopWidth);
var max = parameters.radius * (1 + options.loopWidth);
if (options.innerSmall)
var size = map(r, min, max, Math.max(0, options.lineWidth - options.variation), options.lineWidth + options.variation);
else
var size = map(Math.abs(r - (max + min) / 2), 0, max - min, Math.max(0, options.lineWidth - options.variation), options.lineWidth + options.variation);
context.beginPath();
context.lineWidth = 1;
context[solid ? "fillStyle" : "strokeStyle"] = "hsla(" + (90 - map(i, 0, Math.PI * 2, 0, 360)) + ", 100%, 50%, " + (Math.min(100, (now / 2) - ((stage * 1000) + 100)) / 100) + ")";
context.arc(c.x, c.y, Math.max(Math.min((now - (stage * 2500)) / 250, size), 0), 0, Math.PI * 2);
context[solid ? "fill" : "stroke"]();
return c;
}
loop(function() {
context.fillStyle = "hsla(0, 0%, 10%, 0.5)";
context.fillRect(0, 0, canvas.width, canvas.height);
var progress = (new Date).getTime() / Math.PI / 180 / options.friction;
if (options.sway)
var sway = map((new Date).getTime() % 10000, 0, 10000, 0, Math.PI);
else
var sway = Math.PI / 2;
var lastA = null;
var lastB = null;
var lastC = null;
var lastD = null;
for (var i = Math.PI; i < Math.PI * 2; i += Math.PI / 30) {
var a = drawCircle(i, progress, 0, true);
if (options.lines > 1) {
var b = drawCircle(i, progress, Math.PI, true);
if (options.lines > 2) {
var c = drawCircle(i, progress, sway, true);
if (options.lines > 3) {
var d = drawCircle(i, progress, sway + Math.PI, true);
}
}
}
var line = Math.min(Math.max((((new Date).getTime() - start) - ((Math.PI * 2 - i) * 2500)) / 500, 0), 3);
if (line) {
context.strokeStyle = "hsla(" + (90 - map(i, 0, Math.PI * 2, 0, 360)) + ", 100%, 50%, " + (Math.min(100, (((new Date).getTime() - start) / 2) - ((((Math.PI * 2) - i) * 1000) + 150)) / 100) + ")";
context.lineWidth = line;
if (lastA) {
context.beginPath();
context.moveTo(a.x, a.y);
context.lineTo(lastA.x, lastA.y);
context.stroke();
}
if (lastB) {
context.beginPath();
context.moveTo(b.x, b.y);
context.lineTo(lastB.x, lastB.y);
context.stroke();
}
if (lastC) {
context.beginPath();
context.moveTo(c.x, c.y);
context.lineTo(lastC.x, lastC.y);
context.stroke();
}
if (lastD) {
context.beginPath();
context.moveTo(d.x, d.y);
context.lineTo(lastD.x, lastD.y);
context.stroke();
}
}
lastA = a;
lastB = b;
lastC = c;
lastD = d;
}
for (var i = 0; i < Math.PI; i += Math.PI / 180) {
drawCircle(i, progress, 0);
if (options.lines > 1) {
drawCircle(i, progress, Math.PI);
if (options.lines > 2) {
drawCircle(i, progress, sway);
if (options.lines > 3) {
drawCircle(i, progress, sway + Math.PI);
}
}
}
}
return true;
});
// ============================== //
// ========== CONTROLS ========== //
// ============================== //
var controls = document.body.appendChild(document.createElement("table"));
controls.style.setProperty("position", "fixed");
controls.style.setProperty("z-index", "1000");
controls.style.setProperty("padding", "4px");
controls.style.setProperty("background-color", "hsla(0, 0%, 100%, 0.5)");
for (var key in options) {
var container = controls.appendChild(document.createElement("tr"));
container.style.setProperty("display", "block");
switch (key) {
case "loopCount":
container.title = "Number of loops when fully expanded";
break;
case "loopWidth":
container.title = "The distance of the lines when fully separated";
break;
case "loopSpace":
container.title = "The amount of non-expanded space left in the circle";
break;
case "loopSpeed":
container.title = "The speed which the loop segments move in and out from the circle";
break;
case "lineWidth":
container.title = "The thickness of each segment of the loop";
break;
case "variation":
container.title = "The size of the circle when it is on the inside of the loop";
break;
case "friction":
container.title = "The speed which the expanded space moves around the circle";
break;
case "lines":
container.title = "Number of contiguous lines that wrap around the center title";
break;
case "innerSmall":
container.title = "Whether the smallest circles are in the center or the inside";
break;
case "sway":
container.title = "Whether the second set of lines moves between the other set";
break;
}
var text = container.appendChild(document.createElement("td"));
text.style.setProperty("display", "inline-block");
text.style.setProperty("width", "100px");
text.style.setProperty("cursor", "help");
text.textContent = key;
var rangeContainer = container.appendChild(document.createElement("td"));
var inputContainer = container.appendChild(document.createElement("td"));
if (typeof options[key] === "number") {
var range = rangeContainer.appendChild(document.createElement("input"));
range.type = "range";
range.min = -1 * (options[key] || 1) * 10;
range.max = (options[key] || 1) * 10;
range.step = (options[key] || 1) / 20;
range.value = options[key];
var input = inputContainer.appendChild(document.createElement("input"));
input.style.setProperty("width", "60px");
input.style.setProperty("text-align", "right");
input.type = "number";
input.step = options[key] / 100;
input.value = options[key];
if (key === "loopCount" || key === "lines")
range.step = input.step = 1;
if (key === "loopCount" || key === "loopWidth" || key === "loopSpace" || key === "lineWidth" || key === "variation" || key === "lines")
range.min = input.min = 0;
if (key === "lines")
range.max = input.max = 4;
var handleChange = function(key, range, input, event) {
if (input.value.endsWith("."))
return;
options[key] = range.value = input.value = parseFloat(event.target.value);
}.bind(null, key, range, input);
range.addEventListener("input", handleChange);
input.addEventListener("input", handleChange);
} else if (typeof options[key] === "boolean") {
var input = inputContainer.appendChild(document.createElement("input"));
input.style.setProperty("width", "60px");
input.style.setProperty("text-align", "right");
input.type = "checkbox";
input.checked = options[key];
input.addEventListener("change", function(key, event) {
options[key] = event.target.checked;
}.bind(null, key));
}
}
var restart = controls.appendChild(document.createElement("button"));
restart.style.setProperty("float", "right");
restart.textContent = "RESTART";
restart.addEventListener("click", function(event) {
start = (new Date()).getTime();
});
</script>
</body>
</html>