Inspired by Vincent Pantaloni, some Lissajous curve drawing.
A Pen by Libertar.io on CodePen.
| <canvas height="1600" width="1600"></canvas> | |
| <canvas height="1600" width="1600"></canvas> |
Inspired by Vincent Pantaloni, some Lissajous curve drawing.
A Pen by Libertar.io on CodePen.
| console.clear(); | |
| const PI = Math.PI, | |
| PI2 = PI * 2, | |
| RATE = 30, | |
| CVS1 = document.querySelector('canvas:nth-child(1)'), | |
| CVS2 = document.querySelector('canvas:nth-child(2)'), | |
| CTX1 = CVS1.getContext('2d'), | |
| CTX2 = CVS2.getContext('2d'), | |
| CVS_DI = CVS1.width, | |
| // How many cols/rows | |
| COUNT = 8, | |
| // Padding | |
| PEN_PAD = CVS_DI * 0.025, | |
| // Radius | |
| PEN_RAD = CVS_DI / ((COUNT + 1) * 2), | |
| // Stroke line width | |
| STROKE = 2, | |
| // Indicator radius | |
| TIP_RAD = 6, | |
| // Coloring | |
| COLOR = '#FFF', | |
| COLOR_OFF = '#333'; | |
| // An input "Pen", influences the x or y of a Group's output | |
| class Pen { | |
| constructor({ rate, x, y }) { | |
| this.di = PEN_RAD * 2; | |
| this.rad = PEN_RAD * 0.7; | |
| this.x = x * this.di; | |
| this.y = y * this.di; | |
| this.cx = this.x + this.di * 0.5; | |
| this.cy = this.y + this.di * 0.5; | |
| this.rateName = rate; | |
| this.rate = RATE * rate; | |
| this.position = 0; | |
| } | |
| tick() { | |
| this.progress = this.position / this.rate; | |
| this._calc(); | |
| this.position++; | |
| } | |
| // Setting the coordinates for the tip of the pen | |
| _calc() { | |
| let deg = 360 * this.progress; | |
| this.tipX = this.cx + this.rad * Math.cos(deg * PI / 180); | |
| this.tipY = this.cy + this.rad * Math.sin(deg * PI / 180); | |
| } | |
| } | |
| // Takes an x pen and y pen and produces an output | |
| class Group { | |
| constructor(penX, penY) { | |
| this.penX = penX; | |
| this.penY = penY; | |
| this.tracker = {}; | |
| this.progress = 0; | |
| } | |
| tick() { | |
| // setting the last coordinates for the output line | |
| this.lastX = this.x; | |
| this.lastY = this.y; | |
| // Setting the current coordinates | |
| this.x = this.penX.tipX + this.penY.x; | |
| this.y = this.penY.tipY + this.penX.y; | |
| // Storing the combination so we never redraw | |
| let key = `${this.lastX}-${this.lastY}-${this.x}-${this.y}`; | |
| // Setting draw to false if this has already been drawn | |
| if (this.tracker[key]) this.draw = false; | |
| // Setting draw to true then storing that this has been drawn | |
| else { this.tracker[key] = true; this.draw = true }; | |
| // Draw the output and the Pens | |
| this._draw(); | |
| this.progress++; | |
| } | |
| _draw() { | |
| this._drawPen(this.penX); | |
| this._drawPen(this.penY); | |
| this._drawOutput(); | |
| } | |
| // Take a pen and draw it, its rate, and an indicator of current position | |
| _drawPen({ rad, di, x, y, cx, cy, tipX, tipY, rateName }) { | |
| // the circle | |
| CTX1.lineWidth = STROKE; | |
| CTX1.strokeStyle = COLOR_OFF; | |
| CTX1.beginPath(); | |
| CTX1.arc(cx, cy, rad, 0, PI2, false); | |
| CTX1.stroke(); | |
| // The current position | |
| CTX1.fillStyle = COLOR; | |
| CTX1.strokeStyle = COLOR; | |
| CTX1.beginPath(); | |
| CTX1.arc(tipX, tipY, TIP_RAD, 0, PI2, false); | |
| CTX1.fill(); | |
| // the rate number | |
| CTX1.fillStyle = COLOR_OFF; | |
| CTX1.font = `lighter ${rad * 0.5}px Helvetica`; | |
| CTX1.textAlign = 'center'; | |
| CTX1.textBaseline = 'middle'; | |
| CTX1.fillText(rateName, cx, cy); | |
| } | |
| // Draw the output of the two pens | |
| _drawOutput() { | |
| // If it hasnt already been drawn yet, | |
| // draw the path on the canvas that isnt erased each frame | |
| if (this.draw) { | |
| CTX2.lineWidth = STROKE; | |
| CTX2.strokeStyle = COLOR; | |
| CTX2.beginPath(); | |
| CTX2.moveTo(this.lastX, this.lastY); | |
| CTX2.lineTo(this.x, this.y); | |
| CTX2.stroke(); | |
| } | |
| // Draw the indicator on the refreshing canvas | |
| CTX1.fillStyle = COLOR; | |
| CTX1.beginPath(); | |
| CTX1.arc(this.x, this.y, TIP_RAD, 0, PI2, false); | |
| CTX1.fill(); | |
| } | |
| } | |
| let pensX = [], | |
| pensY = [], | |
| groups = []; | |
| // Generating the pens | |
| for (let x = 0; x < COUNT; x++) { | |
| let penX = new Pen({ rate: x + 1, x: x + 1, y: 0 }), | |
| penY = new Pen({ rate: x + 1, x: 0, y: x + 1 }); | |
| pensX.push(penX); | |
| pensY.push(penY); | |
| } | |
| // Generating the groups | |
| for (let x = 0; x < pensY.length; x++) | |
| for (let y = 0; y < pensX.length; y++) | |
| groups.push(new Group(pensX[x], pensY[y])); | |
| // Start running | |
| run(); | |
| function run() { | |
| // Clear the refreshing canvas | |
| CTX1.clearRect(0, 0, CVS_DI, CVS_DI); | |
| // For each pen | |
| for (let i = 0; i < pensX.length; i++) { | |
| // Tick them forward to get new coords | |
| pensX[i].tick(); | |
| pensY[i].tick(); | |
| // Draw the axis | |
| CTX1.lineWidth = STROKE; | |
| CTX1.strokeStyle = COLOR_OFF; | |
| CTX1.beginPath(); | |
| CTX1.moveTo(pensX[i].tipX, 0); | |
| CTX1.lineTo(pensX[i].tipX, CVS_DI); | |
| CTX1.stroke(); | |
| CTX1.beginPath(); | |
| CTX1.moveTo(0, pensY[i].tipY); | |
| CTX1.lineTo(CVS_DI, pensY[i].tipY); | |
| CTX1.stroke(); | |
| } | |
| // Tick each group (draws them as well) | |
| groups.forEach(group => group.tick()); | |
| // Run it again | |
| requestAnimationFrame(run); | |
| } |
| html, body { | |
| height: 100%; | |
| } | |
| body { | |
| background: #000; | |
| } | |
| canvas { | |
| width: 800px; | |
| height: auto; | |
| max-width: 95%; | |
| position: absolute; | |
| display: block; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| &:first-child { | |
| // background: white; | |
| } | |
| } |