Skip to content

Instantly share code, notes, and snippets.

@tophtucker
Last active December 1, 2015 03:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tophtucker/540fba1ce9bb9aeb2dac to your computer and use it in GitHub Desktop.
Save tophtucker/540fba1ce9bb9aeb2dac to your computer and use it in GitHub Desktop.
Streaky Text II

Crappy ripoff of FizzyText (seen here, source here, my attempted explanation here).

In this one, there are actually three canvases:

  • One to get collision-detection info from
  • One that doesn't get cleared every frame, to show streaks
  • One that gets cleared every frame, to show the moving endpoints

I set everything to the same color; the particles aren't picking up the color of what's underneath.

// http://mrl.nyu.edu/~perlin/noise/
var ImprovedNoise = function () {
var p = [151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,
23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,
174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,
133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,
89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,
202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,
248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,
178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,
14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,
93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180];
for ( var i = 0; i < 256 ; i++ ) {
p[ 256 + i ] = p[ i ];
}
function fade( t ) {
return t * t * t * ( t * ( t * 6 - 15 ) + 10 );
}
function lerp( t, a, b ) {
return a + t * ( b - a );
}
function grad( hash, x, y, z ) {
var h = hash & 15;
var u = h < 8 ? x : y, v = h < 4 ? y : h == 12 || h == 14 ? x : z;
return ( ( h & 1 ) == 0 ? u : -u ) + ( ( h & 2 ) == 0 ? v : -v );
}
return {
noise: function ( x, y, z ) {
var floorX = Math.floor( x ), floorY = Math.floor( y ), floorZ = Math.floor( z );
var X = floorX & 255, Y = floorY & 255, Z = floorZ & 255;
x -= floorX;
y -= floorY;
z -= floorZ;
var xMinus1 = x -1, yMinus1 = y - 1, zMinus1 = z - 1;
var u = fade( x ), v = fade( y ), w = fade( z );
var A = p[ X ] + Y, AA = p[ A ] + Z, AB = p[ A + 1 ] + Z, B = p[ X + 1 ] + Y, BA = p[ B ] + Z, BB = p[ B + 1 ] + Z;
return lerp( w, lerp( v, lerp( u, grad( p[ AA ], x, y, z ),
grad( p[ BA ], xMinus1, y, z ) ),
lerp( u, grad( p[ AB ], x, yMinus1, z ),
grad( p[ BB ], xMinus1, yMinus1, z ) ) ),
lerp( v, lerp( u, grad( p[ AA + 1 ], x, y, zMinus1 ),
grad( p[ BA + 1 ], xMinus1, y, z - 1 ) ),
lerp( u, grad( p[ AB + 1 ], x, yMinus1, zMinus1 ),
grad( p[ BB + 1 ], xMinus1, yMinus1, zMinus1 ) ) ) );
}
}
}
var currentRandom = Math.random;
// Pseudo-random generator
function Marsaglia(i1, i2) {
// from http://www.math.uni-bielefeld.de/~sillke/ALGORITHMS/random/marsaglia-c
var z=i1 || 362436069, w= i2 || 521288629;
var nextInt = function() {
z=(36969*(z&65535)+(z>>>16)) & 0xFFFFFFFF;
w=(18000*(w&65535)+(w>>>16)) & 0xFFFFFFFF;
return (((z&0xFFFF)<<16) | (w&0xFFFF)) & 0xFFFFFFFF;
};
this.nextDouble = function() {
var i = nextInt() / 4294967296;
return i < 0 ? 1 + i : i;
};
this.nextInt = nextInt;
}
Marsaglia.createRandomized = function() {
var now = new Date();
return new Marsaglia((now / 60000) & 0xFFFFFFFF, now & 0xFFFFFFFF);
};
// Noise functions and helpers
function PerlinNoise(seed) {
var rnd = seed !== undefined ? new Marsaglia(seed) : Marsaglia.createRandomized();
var i, j;
// http://www.noisemachine.com/talk1/17b.html
// http://mrl.nyu.edu/~perlin/noise/
// generate permutation
var p = new Array(512);
for(i=0;i<256;++i) { p[i] = i; }
for(i=0;i<256;++i) { var t = p[j = rnd.nextInt() & 0xFF]; p[j] = p[i]; p[i] = t; }
// copy to avoid taking mod in p[0];
for(i=0;i<256;++i) { p[i + 256] = p[i]; }
function grad3d(i,x,y,z) {
var h = i & 15; // convert into 12 gradient directions
var u = h<8 ? x : y,
v = h<4 ? y : h===12||h===14 ? x : z;
return ((h&1) === 0 ? u : -u) + ((h&2) === 0 ? v : -v);
}
function grad2d(i,x,y) {
var v = (i & 1) === 0 ? x : y;
return (i&2) === 0 ? -v : v;
}
function grad1d(i,x) {
return (i&1) === 0 ? -x : x;
}
function lerp(t,a,b) { return a + t * (b - a); }
this.noise3d = function(x, y, z) {
var X = Math.floor(x)&255, Y = Math.floor(y)&255, Z = Math.floor(z)&255;
x -= Math.floor(x); y -= Math.floor(y); z -= Math.floor(z);
var fx = (3-2*x)*x*x, fy = (3-2*y)*y*y, fz = (3-2*z)*z*z;
var p0 = p[X]+Y, p00 = p[p0] + Z, p01 = p[p0 + 1] + Z, p1 = p[X + 1] + Y, p10 = p[p1] + Z, p11 = p[p1 + 1] + Z;
return lerp(fz,
lerp(fy, lerp(fx, grad3d(p[p00], x, y, z), grad3d(p[p10], x-1, y, z)),
lerp(fx, grad3d(p[p01], x, y-1, z), grad3d(p[p11], x-1, y-1,z))),
lerp(fy, lerp(fx, grad3d(p[p00 + 1], x, y, z-1), grad3d(p[p10 + 1], x-1, y, z-1)),
lerp(fx, grad3d(p[p01 + 1], x, y-1, z-1), grad3d(p[p11 + 1], x-1, y-1,z-1))));
};
this.noise2d = function(x, y) {
var X = Math.floor(x)&255, Y = Math.floor(y)&255;
x -= Math.floor(x); y -= Math.floor(y);
var fx = (3-2*x)*x*x, fy = (3-2*y)*y*y;
var p0 = p[X]+Y, p1 = p[X + 1] + Y;
return lerp(fy,
lerp(fx, grad2d(p[p0], x, y), grad2d(p[p1], x-1, y)),
lerp(fx, grad2d(p[p0 + 1], x, y-1), grad2d(p[p1 + 1], x-1, y-1)));
};
this.noise1d = function(x) {
var X = Math.floor(x)&255;
x -= Math.floor(x);
var fx = (3-2*x)*x*x;
return lerp(fx, grad1d(p[X], x), grad1d(p[X+1], x-1));
};
}
// these are lifted from Processing.js
// processing defaults
var noiseProfile = { generator: undefined, octaves: 4, fallout: .8, seed: undefined};
function noise(x, y, z) {
if(noiseProfile.generator === undefined) {
// caching
noiseProfile.generator = new PerlinNoise(noiseProfile.seed);
}
var generator = noiseProfile.generator;
var effect = 1, k = 1, sum = 0;
for(var i=0; i<noiseProfile.octaves; ++i) {
effect *= noiseProfile.fallout;
switch (arguments.length) {
case 1:
sum += effect * (1 + generator.noise1d(k*x))/2; break;
case 2:
sum += effect * (1 + generator.noise2d(k*x, k*y))/2; break;
case 3:
sum += effect * (1 + generator.noise3d(k*x, k*y, k*z))/2; break;
}
k *= 2;
}
return sum;
};
<!doctype html>
<html>
<head>
<style>
canvas {
position: absolute;
top: 0;
left: 0;
}
</style>
<title>StreakyText</title>
<script src='improvedNoise.js'></script>
<script src='main.js'></script>
<script>
window.onload = function() {
var streakyText = new StreakyText('Streaky Text!');
};
</script>
</head>
<body>
<div id='streakytext'></div>
</body>
</html>
// TOTAL RIPOFF OF FIZZYTEXT
// https://github.com/dataarts/dat.gui/blob/gh-pages/docs/demo.js
// (still has a ton of vestigial elements thereof)
function StreakyText(message) {
var 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.03; // how fast do particles change size?
this.minR = 0.2;
this.maxR = 1.5;
this.minV = .4;
this.maxV = 2; // how big can they get?
this.noiseStrength = 10; // how turbulent is the flow?
this.speed = 0.4; // how fast do particles move?
this.displayOutline = true; // should we draw the message as a stroke?
this.framesRendered = 0;
////////////////////////////////////////////////////////////////
var _this = this;
var width = 550;
var height = 200;
var textAscent = 101;
var textOffsetLeft = 20;
var noiseScale = 300;
var frameTime = 30;
// var colors = ["#00aeff", "#0fa954", "#54396e", "#e61d5f"];
var colors = ["#00aeff"]
// This is the context we use to get a bitmap of text using
// the getImageData function.
var r = document.createElement('canvas');
var s = r.getContext('2d');
// This is the context we actually use to draw.
var c = document.createElement('canvas');
var g = c.getContext('2d');
// And this one is used to leave behind the 'streaks'; it doesn't get cleared, see...
var c2 = document.createElement('canvas');
var g2 = c2.getContext('2d');
r.setAttribute('width', width);
c.setAttribute('width', width);
c2.setAttribute('width', width);
r.setAttribute('height', height);
c.setAttribute('height', height);
c2.setAttribute('height', height);
// Add our demo to the HTML
document.getElementById('streakytext').appendChild(c);
document.getElementById('streakytext').appendChild(c2);
// Stores bitmap image
var pixels = [];
// Stores a list of particles
var particles = [];
// Set g.font to the same font as the bitmap canvas, incase we
// want to draw some outlines.
s.font = g.font = g2.font = "800 82px helvetica, arial, sans-serif";
// g2.fillStyle = 'rgba(0, 0, 0, 0.2)';
g2.globalAlpha = .1;
// Instantiate some particles
for (var i = 0; i < 100; 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.
var createBitmap = function (msg) {
s.fillStyle = "#fff";
s.fillRect(0, 0, width, height);
s.fillStyle = "#222";
s.fillText(msg, textOffsetLeft, textAscent);
// Pull reference
var imageData = s.getImageData(0, 0, width, height);
pixels = imageData.data;
};
// Called once per frame, updates the animation.
var render = function () {
that.framesRendered ++;
g.clearRect(0, 0, width, height);
if (_this.displayOutline) {
g.globalCompositeOperation = "source-over";
g.strokeStyle = "#000";
g.lineWidth = .5;
g.strokeText(message, textOffsetLeft, textAscent);
g.fillText(message, textOffsetLeft, textAscent);
}
g.globalCompositeOperation = "darker";
g2.globalCompositeOperation = "darker";
for (var i = 0; i < particles.length; i++) {
g.fillStyle = colors[i % colors.length];
g2.fillStyle = colors[i % colors.length];
particles[i].render();
}
};
// Returns x, y coordinates for a given index in the pixel array.
var 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.
var getColor = function (x, y) {
var base = (Math.floor(y) * width + Math.floor(x)) * 4;
var 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 = 0.2;
this.v = 0.1;
this.constrain = function (v, o1, o2) {
if (v < o1) v = o1;
else if (v > o2) v = o2;
return v;
};
// Called every frame
this.render = function () {
// What color is the pixel we're sitting on top of?
var c = getColor(this.x, this.y);
// Where should we move?
var angle = noise(this.x / noiseScale, this.y / noiseScale) * _this.noiseStrength;
// Are we within the boundaries of the image?
var onScreen = this.x > 0 && this.x < width &&
this.y > 0 && this.y < height;
var isBlack = c != "rgb(255,255,255)" && onScreen;
// If we're on top of a black pixel, slow.
// If not, speed up.
if (isBlack) {
// this.v -= _this.growthSpeed;
this.r += 10 * _this.growthSpeed;
} else {
// this.v += _this.growthSpeed;
this.r -= _this.growthSpeed;
}
this.r = this.constrain(this.r, _this.minR, _this.maxR);
this.v = this.constrain(this.v, _this.minV, _this.maxV);
this.y += this.v;
// Change our position based on the flow field and our
// explode velocity.
this.x += Math.cos(angle) * this.v;
this.y += -Math.sin(angle) * this.v;
// Once we fall off the bottom, respawn at random x-coord along top
// if(this.y > height) {
// this.x = Math.random() * width;
// this.y = 0;
// }
// 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;
// If we're tiny, keep moving around until we find a black
// pixel.
if (this.r <= .2) {
this.x = Math.random() * width;
this.y = Math.random() * height;
return; // Don't draw!
}
// Draw the circle.
g.beginPath();
g.arc(this.x, this.y, this.r, 0, Math.PI * 2, false);
g.fill();
// Draw the streak.
g2.beginPath();
g2.arc(this.x, this.y, this.r, 0, Math.PI * 2, false);
g2.fill();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment