const noise = require('./fizzy-noise'); function fizzyText(message) { const that = this; // These are the variables that we manipulate with gui-dat. // Notice they're all defined with "this". That makes them public. // Otherwise, gui-dat can't see them. this.growthSpeed = 0.4; // how fast do particles change size? this.minSize = 1; this.maxSize = 4; // how big can they get? this.noiseStrength = 10; // how turbulent is the flow? this.speed = 0.4; // how fast do particles move? this.displayOutline = false; // should we draw the message as a stroke? this.framesRendered = 0; // ////////////////////////////////////////////////////////////// const _this = this; const width = 550; const height = 200; const textAscent = 101; const textOffsetLeft = 80; const noiseScale = 300; const frameTime = 30; const colors = ['#000000', '#1A1A1A', '#163C50', '#205A79', '#2A78A2']; // This is the context we use to get a bitmap of text using // the getImageData function. const r = document.createElement('canvas'); const s = r.getContext('2d'); // This is the context we actually use to draw. const c = document.createElement('canvas'); const g = c.getContext('2d'); r.setAttribute('width', width); c.setAttribute('width', width); r.setAttribute('height', height); c.setAttribute('height', height); // Add our demo to the HTML document.getElementById('fizzytext').appendChild(c); // Stores bitmap image let pixels = []; // Stores a list of particles const particles = []; // Set g.font to the same font as the bitmap canvas, incase we // want to draw some outlines. s.font = g.font = '800 82px monospace, monospace'; // Instantiate some particles for (let i = 0; i < 1000; i++) { particles.push(new Particle(Math.random() * width, Math.random() * height)); } // This function creates a bitmap of pixels based on your message // It's called every time we change the message property. const createBitmap = function (msg) { s.fillStyle = '#fff'; s.fillRect(0, 0, width, height); s.fillStyle = '#222'; s.fillText(msg, textOffsetLeft, textAscent); // Pull reference const imageData = s.getImageData(0, 0, width, height); pixels = imageData.data; }; // Called once per frame, updates the animation. const render = function () { that.framesRendered++; g.clearRect(0, 0, width, height); if (_this.displayOutline) { g.globalCompositeOperation = 'source-over'; g.strokeStyle = '#000'; g.lineWidth = 0.5; g.strokeText(message, textOffsetLeft, textAscent); } g.globalCompositeOperation = 'darker'; for (let i = 0; i < particles.length; i++) { g.fillStyle = colors[i % colors.length]; particles[i].render(); } }; // Returns x, y coordinates for a given index in the pixel array. const getPosition = function (i) { return { x: (i - (width * 4) * Math.floor(i / (width * 4))) / 4, y: Math.floor(i / (width * 4)), }; }; // Returns a color for a given pixel in the pixel array. const getColor = function (x, y) { const base = (Math.floor(y) * width + Math.floor(x)) * 4; const c = { r: pixels[base + 0], g: pixels[base + 1], b: pixels[base + 2], a: pixels[base + 3], }; return `rgb(${c.r},${c.g},${c.b})`; }; this.message = message; createBitmap(message); var loop = function () { requestAnimationFrame(loop); render(); }; // This calls the render function every 30 milliseconds. loop(); // This class is responsible for drawing and moving those little // colored dots. function Particle(x, y, c) { // Position this.x = x; this.y = y; // Size of particle this.r = 1; // This velocity is used by the explode function. this.vx = 0; this.vy = 0; this.constrain = function constrainFn(v, o1, o2) { if (v < o1) v = o1; else if (v > o2) v = o2; return v; }; // Called every frame this.render = function renderFrame() { // What color is the pixel we're sitting on top of? const c = getColor(this.x, this.y); // Where should we move? const angle = noise(this.x / noiseScale, this.y / noiseScale) * _this.noiseStrength; // var angle = -Math.PI/2; // Are we within the boundaries of the image? const onScreen = this.x > 0 && this.x < width && this.y > 0 && this.y < height; const isBlack = c !== 'rgb(255,255,255)' && onScreen; // If we're on top of a black pixel, grow. // If not, shrink. if (isBlack) { this.r += _this.growthSpeed; } else { this.r -= _this.growthSpeed; } // This velocity is used by the explode function. this.vx *= 0.5; this.vy *= 0.5; // Change our position based on the flow field and our // explode velocity. this.x += Math.cos(angle) * _this.speed + this.vx; this.y += -Math.sin(angle) * _this.speed + this.vy; if (this.r > _this.maxSize) { this.r = _this.maxSize; } else if (this.r < 0) { this.r = 0; this.x = Math.random() * width; this.y = Math.random() * height; return false; } // this.r = 3; // debugger // console.log(DAT.GUI.constrain(this.r, 0, _this.maxSize)); // this.r = this.constrain(this.r, _this.minSize, _this.maxSize); // If we're tiny, keep moving around until we find a black // pixel. if (this.r <= 0) { this.x = Math.random() * width; this.y = Math.random() * height; return false; // Don't draw! } // If we're off the screen, go over to other side if (this.x < 0) this.x = width; if (this.x > width) this.x = 0; if (this.y < 0) this.y = height; if (this.y > height) this.y = 0; // Draw the circle. g.beginPath(); // g.arc(this.x, this.y, this.r, 0, Math.PI * 2, false); g.rect(this.x, this.y, this.r, this.r); g.fill(); return true; }; } } module.exports = fizzyText;