Skip to content

Instantly share code, notes, and snippets.

@lostintangent
Created February 7, 2020 18:35
Show Gist options
  • Save lostintangent/c81960f3c3521209efb14436d71acf76 to your computer and use it in GitHub Desktop.
Save lostintangent/c81960f3c3521209efb14436d71acf76 to your computer and use it in GitHub Desktop.
lines
"use strict";
window.addEventListener("load",function() {
let canv, ctx;
let lines;
let maxx, maxy;
let yLines;
let restart;
/********************************************************
parameters - play with them
********************************************************/
const bgColor = '#004'; // background color
const nbLines = 6; // number of lines
const pixelsPerFrame = 3; // sliding speed
const interLine = 50; // distance between lines - in pixels
const lineWidth = 5; // line thickness
const transitionCoeff = 3 ; // coeff for transition
const coeffBezier = 0.5; // coeff for Bezier curve
//********************************************************
// shortcuts for Math
const mrandom = Math.random;
const mfloor = Math.floor;
const mceil = Math.ceil;
const mround = Math.round;
const mabs = Math.abs;
const mmin = Math.min;
const mmax = Math.max;
const mPI = Math.PI;
const mPIS2 = Math.PI / 2;
const m2PI = Math.PI * 2;
const msin = Math.sin;
const mcos = Math.cos;
const matan2 = Math.atan2;
const mhypot = Math.hypot;
const msqrt = Math.sqrt;
//------------------------------------------------------------------------
function Line(index) {
/* creates a new, flat line locate on the indexth line
Adds this line to the lines array
*/
this.index = index; // location in lines
lines[index] = this; // adds new line to lines
this.description = [{kl: index}];
/* kl alone : horizontal line throughout the window, at the given height
{kg, kd, xg, dx} : transition from line kg to line kd, beginning at pixel
x = xg and ending at pixel xg + dx
*/
this.color = `hsl(${alea(360)}, 100%, 50%)`;
} // Line
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Line.prototype.lD = function() {
/* tells on which display line we are on the right side of the window
return a line number
*/
let elemD = this.description[this.description.length - 1]; // last element of this line
return (typeof elemD.kl == 'undefined') ? elemD.kd : elemD.kl;
} // Line.prototype.lD
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Line.prototype.addTransition = function(kd, xg, dx) {
/* adds a transition towards line kd, beginning at pixel xg and lasting a duration of dx
*/
let tra = { kg: this.lD(), kd: kd, xg: xg, dx: dx };
if (this.description.length == 1 && typeof this.description[0].kl != 'undefined') {
this.description.pop(); // There was a single line : destroy it
}
this.description.push(tra); // add a new transition
} // Line.prototype.addTransition
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Line.prototype.draw = function() {
let x0, x1, x2, x3, y0, y3;
ctx.beginPath();
ctx.lineWidth = lineWidth;
ctx.strokeStyle = this.color;
if (this.description.length == 1 && typeof this.description[0].kl != 'undefined') {
y0 = yLines[this.description[0].kl];
ctx.moveTo (0, y0);
ctx.lineTo (maxx, y0);
ctx.stroke();
return; // that's enough
} // case of simple line
/* compound line
begin by straight line if required */
y0 = yLines[this.description[0].kg];
x0 = this.description[0].xg;
if (x0 > 0) {
ctx.moveTo (0, y0);
ctx.lineTo (x0, y0);
} else {
ctx.moveTo (x0, y0);
}
/* draw Bézier curve + following horizontal piece of line if required */
this.description.forEach ((descr, kdesc) => {
x1 = x0 + descr.dx * coeffBezier;
x3 = x0 + descr.dx;
x2 = x3 - descr.dx * coeffBezier;
y3 = yLines[descr.kd];
ctx.bezierCurveTo (x1, y0, x2, y3, x3, y3);
/* overflow to the right, no need go further */
if (x3 >= maxx) {
ctx.stroke();
return;
}
// If it was the last piece, draw horizontal line till the right side
if (kdesc == this.description.length - 1) {
ctx.lineTo (maxx, y3);
ctx.stroke();
return;
} // if end of line
// else, draw horizontal till beginning of next Bezier
y0 = y3; // next line for future
x0 = this.description [kdesc + 1].xg;
ctx.lineTo (x0, y0); // horizontal till beginning of next Bezier
}); // this.description.forEach
} // Line.prototype.draw
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Line.prototype.slide = function(npix) {
// returns rightmost pixel
if (typeof this.description[0].kl != 'undefined') return 0; // simple line, nothing to do
this.description.forEach(descr => {
descr.xg -= npix;
}) // this.description.forEach
let desc0 = this.description[0];
if (desc0.xg + desc0.dx <= 0) { // bézier disappears at the left side, delete it
this.description.shift();
if (this.description.length == 0) { // nothing left, add simple line
this.description[0] = {kl: desc0.kd};
return 0;
}
}
desc0 = this.description[this.description.length - 1];
return desc0.xg + desc0.dx;
} // Line.prototype.slide
//------------------------------------------------------------------------
// end of class Line
//------------------------------------------------------------------------
function newCrossing() {
let nOrder, ge;
do {
nOrder = shuffle(); // picks a new random order of lines
/* calculates highest jump */
ge = lines.reduce (
(maxJump, line) => {
let jump = mabs(line.lD() - nOrder[line.index]);
return (jump > maxJump) ? jump : maxJump;
}, 0);
} while (ge == 0); // we want at least one transition !
let dxMax = (ge + 1) / 2 * transitionCoeff * interLine;
lines.forEach ((line, index) => {
let jump = mabs(line.lD() - nOrder[line.index]);
if (jump > 0) { // if this line actually moves
let dx = (jump + 1) / 2 * transitionCoeff * interLine;
let orgx = (dxMax - dx) / 2; // where the transition will begin for this line
line.addTransition(nOrder[line.index], maxx + orgx, dx);
} // if this line actually moves
}); // lines.forEach
} // newCrossing
//------------------------------------------------------------------------
function size() {
let orgy;
maxx = window.innerWidth - 8; // -8 for a 4 pixels margin
maxy = window.innerHeight - 8;
canv.style.left = (window.innerWidth - maxx) / 2 + 'px';
canv.style.top = (window.innerHeight - maxy) / 2 + 'px';
canv.width = maxx;
canv.height = maxy;
orgy = (maxy - (nbLines - 1) * interLine) / 2
yLines = [];
for (let k = 0; k < nbLines; ++k) yLines[k] = orgy + k * interLine;
} // size
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function drawLines() {
ctx.clearRect(0, 0, maxx, maxy);
lines.forEach(line => line.draw());
} // function drawLines()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
let slideLines = (function() {
/* makes lines slide
if there is nothing left on the right of the window, creates a new crossing */
let posnul = maxx * alea (0.8, 0.95);
return function (npix) {
let posd;
let maxd = 0;
lines.forEach(line => {
posd = line.slide(npix);
if (posd > maxd) maxd = posd;
}); // lines.forEach
let dist = posnul - maxd;
if (dist < 0) return; // not finished yet
newCrossing();
posnul = maxx * alea (0.99, 0.999);
}
})(); // function slideLines()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function startOver() {
size(); // resize
lines = [];
for (let k = 0; k < nbLines ; ++k) {
new Line(k);
}
} // startOver
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function alea (min, max) {
if (typeof max == 'undefined') return min * mrandom();
return min + (max - min) * mrandom();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function intAlea (min, max) {
if (typeof max == 'undefined') {
max = min; min = 0;
}
return mfloor(min + (max - min) * mrandom());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function shuffle() {
let tb = [];
let k, r;
for (k = 0; k < nbLines; ++k) tb[k] = k;
for (k = nbLines - 1; k > 0; --k) {
r = intAlea(0, k + 1);
[tb[r], tb[k]] = [tb[k], tb[r]]
} // for k
return tb
} // function shuffle
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function clickCanvas() {
restart = true;
} // clickCanvas
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function resizeWindow() {
restart = true;
} // clickCanvas
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function animate(chrono) {
if (restart) {
startOver();
}
if (maxx > 10) {
restart = false;
slideLines(pixelsPerFrame);
drawLines();
}
window.requestAnimationFrame(animate);
} // animate
//------------------------------------------------------------------------
//------------------------------------------------------------------------
// beginning of execution
{
document.body.style.backgroundColor = bgColor;
canv = document.createElement('canvas');
canv.style.position = "absolute";
canv.addEventListener('click',clickCanvas);
document.body.appendChild(canv);
ctx = canv.getContext('2d');
} // CANVAS creation
startOver();
window.addEventListener('resize',resizeWindow);
animate();
}); // window load listener
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment