(function() {
"use strict";
var values = {
$x: 0,
$y: 0,
$r: 0,
$sway: 0,
$time: Date.now()
};
var performance = (window.performance || {
offset: Date.now(),
now: function() {
return Date.now() - this.offset;
}
});
function loop(func) {
var last = performance.now();
function frame(timestamp) {
if (func(timestamp - last)) {
last = timestamp;
window.requestAnimationFrame(frame);
}
}
window.requestAnimationFrame(frame);
}
function clamp(num, min, max) {
return Math.max(min, Math.min(max, num));
}
function map(num, in_min, in_max, out_min, out_max) {
return clamp((num - in_min) * (out_max - out_min) / (in_max - in_min) + out_min, out_min, out_max);
}
var chevron = document.body.appendChild(document.createElement("div"));
chevron.classList.add("chevron");
chevron.title = "Click to Scroll";
var canvas = document.body.appendChild(document.createElement("canvas"));
var context = canvas.getContext("2d");
var offscreenCanvas = document.createElement("canvas");
var offscreenContext = offscreenCanvas.getContext("2d");
function handleResize() {
var width = canvas.scrollWidth * window.devicePixelRatio;
var height = canvas.scrollHeight * window.devicePixelRatio;
if (width === canvas.width && height === canvas.height)
return;
canvas.width = offscreenCanvas.width = width;
canvas.height = offscreenCanvas.height = height;
values.$x = width / 2;
values.$y = height / 2;
values.$r = Math.min(values.$x, values.$y) * 4 / 5;
}
handleResize();
window.addEventListener("resize", handleResize);
function Shape(angle) {
this._angle = angle;
this._hue = Math.round(90 - map(this._angle, 0, Math.PI * 2, 0, 360));
this._state = -1;
}
Shape.prototype.$draw = function(delta) {
this._state += delta / 2500;
};
Shape.prototype.$color = function() {
return "hsl(" + this._hue + ", 100%, 50%)";
};
function Circle(angle, offset, sway) {
Shape.call(this, angle);
this._offset = offset;
this._sway = sway;
}
Circle.prototype = Object.create(Shape.prototype);
Circle.prototype.constructor = Circle;
Circle.prototype.$draw = function(delta) {
Shape.prototype.$draw.call(this, delta);
if (this._sway)
this._offset += values.$sway / 500;
var r = values.$r * (1 + (0.125 * clamp(this._state, 0, 1) * Math.cos((9 * this._angle) + (values.$time / 1000) + this._offset)));
this.$x = values.$x + (r * Math.sin(this._angle));
this.$y = values.$y - (r * Math.cos(this._angle));
this.$r = clamp(this._state * 10 * window.devicePixelRatio, 0, map(r, values.$r * 0.875, values.$r * 1.125, values.$r / 60, values.$r / 20));
};
function FilledCircle(angle, offset, sway) {
Circle.call(this, angle, offset, sway);
this._state *= (Math.PI * 2) - this._angle;
this._state -= 0.15;
}
FilledCircle.prototype = Object.create(Circle.prototype);
FilledCircle.prototype.constructor = FilledCircle;
FilledCircle.prototype.$draw = function(delta) {
Circle.prototype.$draw.call(this, delta);
if (!this.$r)
return;
offscreenContext.beginPath();
offscreenContext.fillStyle = this.$color();
offscreenContext.arc(this.$x, this.$y, this.$r, 0, Math.PI * 2);
offscreenContext.fill();
};
function HollowCircle(angle, offset, sway) {
Circle.call(this, angle, offset, sway);
this._state *= this._angle;
this._state -= 0.15;
}
HollowCircle.prototype = Object.create(Circle.prototype);
HollowCircle.prototype.constructor = HollowCircle;
HollowCircle.prototype.$draw = function(delta) {
Circle.prototype.$draw.call(this, delta);
if (!this.$r)
return;
offscreenContext.beginPath();
offscreenContext.lineWidth = 1.5 * window.devicePixelRatio;
offscreenContext.strokeStyle = this.$color();
offscreenContext.arc(this.$x, this.$y, this.$r, 0, Math.PI * 2);
offscreenContext.stroke();
};
function Line(angle, head, tail) {
Shape.call(this, angle);
this._head = head;
this._tail = tail;
this._state *= (Math.PI * 2) - angle;
this._state -= 0.15;
}
Line.prototype = Object.create(Shape.prototype);
Line.prototype.constructor = Line;
Line.prototype.$draw = function(delta) {
Shape.prototype.$draw.call(this, delta);
var dx = this._tail.$x - this._head.$x;
var dy = this._tail.$y - this._head.$y;
var length = clamp(this._state * 10, 0, 1);
var lineWidth = clamp(this._state * 5, 0, values.$r / 100);
if (!length || !lineWidth)
return;
offscreenContext.beginPath();
offscreenContext.lineWidth = lineWidth;
offscreenContext.strokeStyle = this.$color();
offscreenContext.moveTo(this._head.$x, this._head.$y);
offscreenContext.lineTo(this._head.$x + (length * dx), this._head.$y + (length * dy));
offscreenContext.stroke();
};
var lines = [];
var circles = [];
function linkHollowCircle(angle, offset, sway) {
circles.push(new FilledCircle(angle, offset, sway));
if (circles[circles.length - 5])
lines.push(new Line(angle - (Math.PI / 60), circles[circles.length - 1], circles[circles.length - 5]));
}
for (var i = Math.PI; i < Math.PI * 2; i += Math.PI / 30) {
linkHollowCircle(i, 0, false);
linkHollowCircle(i, Math.PI, false);
linkHollowCircle(i, 0, true);
linkHollowCircle(i, Math.PI, true);
}
for (var i = 0; i < Math.PI; i += Math.PI / 180) {
circles.push(new HollowCircle(i, 0, false));
circles.push(new HollowCircle(i, Math.PI, false));
circles.push(new HollowCircle(i, 0, true));
circles.push(new HollowCircle(i, Math.PI, true));
}
loop(function(delta) {
values.$time += delta;
values.$sway = map(values.$time % 10000, 0, 10000, 0, Math.PI);
offscreenContext.clearRect(0, 0, values.$x * 2, values.$y * 2);
for (var i = 0; i < lines.length; ++i)
lines[i].$draw(delta);
for (var i = 0; i < circles.length; ++i)
circles[i].$draw(delta);
context.clearRect(0, 0, values.$x * 2, values.$y * 2);
context.drawImage(offscreenCanvas, 0, 0);
return true;
});
})();