jQuery Plasma

Fork me on GitHub

It's always necessary to keep your skills up as a professional developer, and I wanted to do a bit more ES6 with webpack, which I hadn't done for a while. So, I decided to port my Java plasma routine to Javascript just for fun. It also allowed me to test how to make jQuery plugins using ES6.

So, what's plasma? This sort of thing:

Plasma is essentially a colour mapped interference pattern. The interference is caused by a number of moving patterns that are based around sine waves.

Basic Usage

As with other jQuery plugins, include the libraries, create a div into which you want to put the widget and call the $.plasma() function on it.

<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js"></script>
<script type="text/javascript" src="/js/plasma.jquery_plugin.js"></script>

$('#plasma').plasma();

The plasma has various defaults that means is shows up as above, but these can be overridden as described below.

Options

As usual, options can be passed in as a Javascript object when mapping your element to a plasma.

$('#plasma').plasma( { ... options ... } );

Width and Height

Width and height are controlled by the width and height fields. These need to be pixel amounts (without a 'px' suffix):

$('#plasma').plasma({
	width: 300,
	height: 70
}); 

Colour

The plasma comes with some built-in colour maps which can be referenced by name (in the ES6 they are a map of functions, but I've made them available externally by name). The available list is: [RED_GREEN, PINK_YELLOW, RGB, GREY, ZEBRA, MAGNOLIA, STRANGE_WAVES, WHITE_WAVES]. They look like this:

$('#plasma').plasma({
	colourMap: 'RED_GREEN',
	width: 100,
	height: 100
}); 
$('#plasma').plasma({
	colourMap: 'PINK_YELLOW',
	width: 100,
	height: 100
}); 
$('#plasma').plasma({
	colourMap: 'RGB',
	width: 100,
	height: 100
}); 
$('#plasma').plasma({
	colourMap: 'GREY',
	width: 100,
	height: 100
}); 
$('#plasma').plasma({
	colourMap: 'ZEBRA',
	width: 100,
	height: 100
}); 
$('#plasma').plasma({
	colourMap: 'MAGNOLIA',
	width: 100,
	height: 100
}); 
$('#plasma').plasma({
	colourMap: 'STRANGE_WAVES',
	width: 100,
	height: 100
}); 
$('#plasma').plasma({
	colourMap: 'WHITE_WAVES',
	width: 100,
	height: 100
}); 

As the colour maps are just functions that map a greyscale value to an RGBA value, you can also pass in your own colour map function as the colourMap field.

$('#plasma').plasma({
	colourMap: function(v) { 
		return [v,255-v,0,255]; 
	},
	width: 100,
	height: 100
}); 

Scaling

Due to the amount of pixels that Javascript is shifting around and doing calculations on, it is preferable to minimize this pixel count. With the hardware accelerated, bi-linear interpolated image scaling that occurs on today's browsers, it's actually possible to scale a plasma up without too much degradation. For that we have the scale option.

$('#plasma1').plasma({
	width: 100,
	height: 100,
	scale: 1
}); 
$('#plasma2').plasma({
	width: 100,
	height: 100,
	scale: 4
}); 

With a scaling of 4 (a quarter of the full resolution in each dimension), there are 16x less pixels being moved around, which can be important if the area being rendered is large enough. If you look closely, you can begin to see the pixels showing up in the second one, but it barely notices.

Patterns

The patterns that are mixed to form the interference pattern are generated by functions. There are a number of built-in functions in the plugin which can be accessed by name. The generators to use, and the order to aggregate them in, is provided by the plasmas option, which takes a list of strings or objects.

If strings are provided, then these must be one of the names of the built-in pattern generators, which are one of: ["verticalBars","horizontalBars","rotatingZoomingBars","circles"]. They generate patterns as follows:

$('#plasma').plasma({
	colourMap: 'GREY',
	width: 100,
	height: 100,
	plasmas: ["verticalBars"]
}); 
$('#plasma').plasma({
	colourMap: 'GREY',
	width: 100,
	height: 100,
	plasmas: ["horizontalBars"]
}); 
$('#plasma').plasma({
	colourMap: 'GREY',
	width: 100,
	height: 100,
	plasmas: ["rotatingZoomingBars"]
}); 
$('#plasma').plasma({
	colourMap: 'GREY',
	width: 100,
	height: 100,
	plasmas: ["circles"]
}); 

A pattern generator is just an object that has the following method in it: getPlasma(values, time, height, width). values is a flattened array of the pixels which is why it's useful to know the width and height. The time is provided as a means for placing the frame being generated in time; the idea being that higher frame rates will provide more frames with smaller time steps but, overall, the same interference events will occur a the same time regardless of frame rate.

If you want to, you can supply your own pattern generator by giving the plasma field an object containing the appropriate function. Behold this monstrosity:

$('#plasma-pt-custom').plasma({
	colourMap: 'GREY',
	width: 100,
	height: 100,
	plasmas: [{ getPlasma: function(values, time, h, w) {
		var s = 255/w;
		for( var y = 0; y < h; y++ ) {
			for( var x = 0; x < w; x++ ) {
				if( y%2 == 0 ) {
					values[y*w+x] = (x*s + time)%255;
				}
			}
		}
	}}]
}); 

Custom pattern generators can be used in the same array as the names of the patterns if you want to combine with the default ones.

$('#plasma-pt-custom').plasma({
	colourMap: 'RGB',
	width: 100,
	height: 100,
	plasmas: ["verticalBars", "circles",
	 { getPlasma: function(values, time, h, w) {
		var s = 255/w;
		for( var y = 0; y < h; y++ ) {
			for( var x = 0; x < w; x++ ) {
				if( y%2 == 0 ) {
					values[y*w+x] = (x*s + time/10)%255;
				}
			}
		}
	}}]
}); 

Aggregations

After the various individual patterns are generated they are aggregated into a final interference pattern using an aggregation method. There is only one built-in, which is AVERAGE and is set by default. If you want to override it, supply a function to the aggregator field with the following signature: function(plasmas,w). plasmas is an array of each of the flattened pixel maps for each of the individual patterns. The function should return a new array that is w*h long and contains values 0 <= v <= 255.

Here's a simple custom aggregator that takes the maximum of all patterns for each pixel.

$('#plasma').plasma({
	colourMap: 'RGB',
	width: 100,
	height: 100,
	aggregator: function(plasmas,w) {
		var r = [];
		for( var i = 0; i < plasmas[0].length; i++ ) {
			var max = 0;
			for( var p = 0; p < plasmas.length; p++ ) {
				max = Math.max( max, plasmas[p][i] );
			}
			r[i] = max;
		}
		return r;
	}
}); 

Ok, so what's the point?

Well, like I said; there was no point! I did it as a programming exercise. I haven't even optimised much. But I made it available as I thought it might be useful to someone.

For example, you could use it to make a nice title background:

<div id="plasma"><div class="disco">DISCO!</div></div>
<style>
.disco {
	position: absolute;
	text-align: center;
	width: 400px;
	color: white;
	margin: 0
}
#plasma canvas {
	position: relative;
	z-index: -1;
	margin: 0;
}
</style>
<script>
$('#plasma').plasma({
	colourMap: 'PINK_YELLOW',
	width: 300,
	height: 70
}); 
</script>
DISCO!

Or using the CSS3 mix-blend-mode, create a nice matte effect:

<div id="plasma"><div class="volcano">VOLCANO</div></div>
<style>
.volcano {
	position: absolute;
	text-align: center;
	width: 400px;
	margin: 0;
	mix-blend-mode: multiply;
	color: white;
	background: black;
}
#plasma canvas {
	position: relative;
	z-index: -1;
	margin: 0;
}
</style>
<script>
$('#plasma').plasma({
	colourMap: function(v) {
		return [255,255-v,0,255];
	},
	width: 300,
	height: 70
}); 
</script>
VOLCANO

Compatibility

The plasma is using HTML5 canvas and requestAnimationFrame which requires a modern browser. See this page for compatibility of that function.

Contributing

You can fork the code on Github (just hit the ribbon at the top).

License

The code is released under the MIT license, which basically means you can do whatever you like except pretend it's yours.

Comments