Created
April 3, 2023 21:28
-
-
Save richlysakowski/a2c356e97fa8c1757a2c0bec7269c116 to your computer and use it in GitHub Desktop.
Text Flow Field
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<canvas id="canvas1"></canvas> | |
<canvas id="canvas1"></canvas> | |
<p>This is an advanced flow field example. Step-by-step process explained for beginners here: <a href='https://www.youtube.com/frankslaboratory' target='_blank'>youtube.com/frankslaboratory</a></p> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const canvas = document.getElementById('canvas1'); | |
const ctx = canvas.getContext('2d'); | |
canvas.width = 1700; | |
canvas.height = 500; | |
// canvas settings | |
ctx.fillStyle = 'white'; | |
ctx.strokeStyle = 'white'; | |
ctx.lineWidth = 1; | |
ctx.letterSpacing = '30px'; | |
class Particle { | |
constructor(effect){ | |
this.effect = effect; | |
this.x = Math.floor(Math.random() * this.effect.width); | |
this.y = Math.floor(Math.random() * this.effect.height); | |
this.speedX; | |
this.speedY; | |
this.speedModifier = Math.floor(Math.random() * 2 + 1); | |
this.history = [{x: this.x, y: this.y}]; | |
this.maxLength = Math.floor(Math.random() * 40 + 10); | |
this.angle = 0; | |
this.newAngle = 0; | |
this.angleCorrector = Math.random() * 0.5 + 0.01; | |
this.timer = this.maxLength * 2; | |
//this.colors = ['#7d0101', '#ad0303', '#db0202', '#ff0303', '#fc2323', '#fc4747', '#fc7e7e', 'white']; | |
//this.colors = ['#f8dc3d', '#f8dc3d', '#f2d322', '#c9ad08', '#917d03', '#574a00', '#fae678', '#fcee9f', '#fff7c7','white']; | |
this.colors = ['rgb(255,255,0)']; | |
this.color = this.colors[Math.floor(Math.random() * this.colors.length)]; | |
} | |
draw(context){ | |
context.beginPath(); | |
context.moveTo(this.history[0].x, this.history[0].y); | |
for (let i = 0; i < this.history.length; i++){ | |
context.lineTo(this.history[i].x, this.history[i].y); | |
} | |
context.strokeStyle = this.color; | |
context.stroke(); | |
} | |
update(){ | |
this.timer--; | |
if (this.timer >= 1){ | |
let x = Math.floor(this.x / this.effect.cellSize); | |
let y = Math.floor(this.y / this.effect.cellSize); | |
let index = y * this.effect.cols + x; | |
if (this.effect.flowField[index]){ | |
this.newAngle = this.effect.flowField[index].colorAngle; | |
if (this.angle > this.newAngle){ | |
this.angle -= this.angleCorrector; | |
} else if (this.angle < this.newAngle){ | |
this.angle += this.angleCorrector; | |
} else { | |
this.angle = this.newAngle; | |
} | |
} | |
this.speedX = Math.cos(this.angle); | |
this.speedY = Math.sin(this.angle); | |
this.x += this.speedX * this.speedModifier; | |
this.y += this.speedY * this.speedModifier; | |
this.history.push({x: this.x, y: this.y}); | |
if (this.history.length > this.maxLength){ | |
this.history.shift(); | |
} | |
} else if (this.history.length > 1){ | |
this.history.shift(); | |
} else { | |
this.reset(); | |
} | |
} | |
reset(){ | |
let attempts = 0; | |
let resetSuccess = false; | |
while (attempts < 20 && !resetSuccess){ | |
attempts++ | |
let testIndex = Math.floor(Math.random() * this.effect.flowField.length); | |
if (this.effect.flowField[testIndex].alpha > 0){ | |
this.x = this.effect.flowField[testIndex].x; | |
this.y = this.effect.flowField[testIndex].y; | |
this.history = [{x: this.x, y: this.y}]; | |
this.timer = this.maxLength * 2; | |
resetSuccess = true; | |
} | |
} | |
if (!resetSuccess){ | |
this.x = Math.random() * this.effect.width; | |
this.y = Math.random() * this.effect.height; | |
this.history = [{x: this.x, y: this.y}]; | |
this.timer = this.maxLength * 2; | |
} | |
} | |
} | |
class Effect { | |
constructor(canvas, ctx){ | |
this.canvas = canvas; | |
this.context = ctx; | |
this.width = this.canvas.width; | |
this.height = this.canvas.height; | |
this.particles = []; | |
this.numberOfParticles = 10000; | |
this.cellSize = 10; | |
this.rows; | |
this.cols; | |
this.flowField = []; | |
this.debug = false; | |
this.text = 'FLOW'; | |
this.init(); | |
window.addEventListener('keydown', e => { | |
if (e.key === 'd') this.debug = !this.debug; | |
}); | |
window.addEventListener('resize', e => { | |
//this.resize(e.target.innerWidth, e.target.innerHeight); | |
}); | |
} | |
drawText(){ | |
this.context.font = '600px Bangers'; | |
this.context.textAlign = 'center'; | |
this.context.textBaseline = 'middle'; | |
const gradient1 = this.context.createLinearGradient(0, 0, this.width, this.height); | |
gradient1.addColorStop(0.2, 'rgb(255,0,0)'); | |
gradient1.addColorStop(0.4, 'rgb(0,255,0)'); | |
gradient1.addColorStop(0.6, 'rgb(150,100,100)'); | |
gradient1.addColorStop(0.8, 'rgb(0,255,255)'); | |
const gradient2 = this.context.createLinearGradient(0, 0, this.width, this.height); | |
gradient2.addColorStop(0.2, 'rgb(255,255,0)'); | |
gradient2.addColorStop(0.4, 'rgb(200,5,50)'); | |
gradient2.addColorStop(0.6, 'rgb(150,255,255)'); | |
gradient2.addColorStop(0.8, 'rgb(255,255,150)'); | |
const gradient3 = this.context.createRadialGradient(this.width * 0.5, this.height * 0.5, 10, this.width * 0.5, this.height * 0.5, this.width); | |
gradient3.addColorStop(0.2, 'rgb(0,0,255)'); | |
gradient3.addColorStop(0.4, 'rgb(200,255,0)'); | |
gradient3.addColorStop(0.6, 'rgb(0,0,255)'); | |
gradient3.addColorStop(0.8, 'rgb(0,0,0)'); | |
this.context.fillStyle = gradient2; | |
this.context.fillText(this.text, this.width * 0.5, this.height * 0.5, this.width); | |
} | |
init(){ | |
// create flow field | |
this.rows = Math.floor(this.height / this.cellSize); | |
this.cols = Math.floor(this.width / this.cellSize); | |
this.flowField = []; | |
// draw text | |
this.drawText(); | |
// scan pixel data | |
const pixels = this.context.getImageData(0, 0, this.width, this.height).data; | |
console.log(pixels); | |
for (let y = 0; y < this.height; y += this.cellSize){ | |
for (let x = 0; x < this.width; x += this.cellSize){ | |
const index = (y * this.width + x) * 4; | |
const red = pixels[index]; | |
const green = pixels[index + 1]; | |
const blue = pixels[index + 2]; | |
const alpha = pixels[index + 3]; | |
const grayscale = (red + green + blue) / 3; | |
const colorAngle = ((grayscale/255) * 6.28).toFixed(2); | |
this.flowField.push({ | |
x: x, | |
y: y, | |
alpha: alpha, | |
colorAngle: colorAngle | |
}); | |
} | |
} | |
// create particles | |
this.particles = []; | |
for (let i = 0; i < this.numberOfParticles; i++){ | |
this.particles.push(new Particle(this)); | |
} | |
this.particles.forEach(particle => particle.reset()); | |
} | |
drawGrid(){ | |
this.context.save(); | |
this.context.strokeStyle = 'white'; | |
this.context.lineWidth = 0.3; | |
for (let c = 0; c < this.cols; c++){ | |
this.context.beginPath(); | |
this.context.moveTo(this.cellSize * c, 0); | |
this.context.lineTo(this.cellSize * c, this.height); | |
this.context.stroke(); | |
} | |
for (let r = 0; r < this.rows; r++){ | |
this.context.beginPath(); | |
this.context.moveTo(0, this.cellSize * r); | |
this.context.lineTo(this.width, this.cellSize * r); | |
this.context.stroke(); | |
} | |
this.context.restore(); | |
} | |
resize(width, height){ | |
this.canvas.width = width; | |
this.canvas.height = height; | |
this.width = this.canvas.width; | |
this.height = this.canvas.height; | |
this.init(); | |
} | |
render(){ | |
if (this.debug) { | |
this.drawGrid(); | |
this.drawText(); | |
} | |
this.context.fillStyle = 'rgba(255,255,255,0.05)'; | |
this.context.fillText(this.text, this.width * 0.5 - 5, this.height * 0.5 + 5, this.width); | |
this.particles.forEach(particle => { | |
particle.draw(this.context); | |
particle.update(); | |
}); | |
} | |
} | |
const effect = new Effect(canvas, ctx); | |
function animate(){ | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
effect.render(); | |
requestAnimationFrame(animate); | |
} | |
animate(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
* { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
} | |
body { | |
background: black; | |
overflow: hidden; | |
} | |
canvas { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
} | |
p, a { | |
color: white; | |
} | |
p { | |
max-width: 30%; | |
padding: 20px; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment