mnml/html-client/lib/fizzy-text.js
2019-03-28 15:23:03 +11:00

221 lines
6.7 KiB
JavaScript

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.8; // 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;