Pen Settings



CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource


Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource


Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.


Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.


                <script src=""></script>
		var slider = new gooSlider('body');
		var l1,l2;
			var c = 'hsla('+ (Math.floor(Math.random()*7) * 50) +',56%,68%,1)';
			if(c != l1 && c != l2){
				slider.wave(c, function(){});
				l2 = l1;
				l1 = c;
			} else {
		var count = 5,
		    cur = 0;
		function start(){
			slider.wave('hsla('+ (Math.floor(Math.random()*7) * 50) +',82%,64%,1)', function(){});
				}, 1500)

<div id="info">Click to spill more paint!</div>


                html, body {
	height: 100%;
	width: 100%;
	padding: 0px;
	margin: 0px;
	overflow: hidden;
	display: flex;

#info {
	margin: auto;
	position: relative;
	z-index: 2;
	color: #ffffff;
	font-family: sans-serif;
	pointer-events: none;
	-webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
     -moz-user-select: none;
     -ms-user-select: none;
    user-select: none

canvas {
	z-index: 1;
	position: absolute;
	top: 0px;
	left: 0px;
	width: 100%;
	height: 100%;


window.requestFrame = (function(){
	return  window.requestAnimationFrame       ||
		window.webkitRequestAnimationFrame ||
		window.mozRequestAnimationFrame    ||
		function( callback ){
		window.setTimeout(callback, 1000 / 60);

var gooSlider = function(parent, id){
	var size = {
		x: $(parent).width(),
		y: $(parent).height()
	var canvas = $('<canvas/>').attr({width: size.x, height: size.y, id: id}).appendTo(parent),
    context = canvas.get(0).getContext("2d");
	var startTime = new Date().getTime(); // timing stuff
	var currentTime = 0; // more timing, it just makes it so it always falls at the same rate
	var dripDist = 100; // distance between each "drip"
	var dripsAmount = Math.floor(size.x / dripDist) + 2; // amount of drips that will go across screen
	waves = []; // this will contain the information for each wave (color, drip positions, etc...)
	this.waves = waves; // making the waves variable available to the outside world
	this.wave = function(settings, call){ // this creates the wave object. You pass through the color of the "wave" and the callback for when the wave covers the screen.
		if(typeof call != 'function') { call = function(){} }
		var it = {
			drips: [],
			callback: call,
			defaults: {
				color: '#99db81',
				minSpeed: 1,
				maxSpeed: 3
			settings: {}
		if(typeof settings == 'object'){
			it.settings = $.extend(true, it.defaults, settings);
		} else {
			it.settings = $.extend(true, it.defaults, {color: settings});
		for(var i = 0; i < dripsAmount; i++){
		waves.push(it); // add the object to the waves array so it's animated.
	function loop(){
		var now = new Date().getTime();
		currentTime = (now - startTime) / 10000; // more timing..
		for(var i = 0; i <= waves.length; i++){ // for each wave
			if(waves[i] != undefined){ // make sure it's still getting animated
				var done = true;  // this tells me if we're on the last animation frame for this wave or not

				var points = []; // this will contain the points for the wave
				for(var c = 0; c < waves[i].drips.length; c++){ // for each drip in the wave
					waves[i].drips[c] += waves[i].settings.minSpeed + ((noise.simplex2((i*dripsAmount)+c,currentTime) + 1) * waves[i].settings.maxSpeed); // add the "speed" of that drop to the y position

					points.push({ // add this point to be animated
						x: dripDist*c, 
						y: waves[i].drips[c]
					if(waves[i].drips[c] < size.y){ // if this point is not to the bottom of the screen yet, set done to false.  Once all points are below the screen, done will be set to true and the animation for this wave will end and the callback will be... called.
						done = false; // ^^
				if(done == true){ // The continuation of the comment above
					waves[i].callback(); // callback
					waves[i].callback = function(){}; // callback
					waves[i-1] = undefined; // stop the waves before this ones animation.  
					// The reason why I do that ^ instead of setting this one to undefined, is to fix an issue where the previous wave will be undrawn before the current wave reaches the bottom.  Which results in random colors flashing at the bottom which is no good.
				} else {
					context.beginPath(); // this is the animation
					context.strokeStyle = waves[i].settings.color; // set the color of the wave
					context.fillStyle = waves[i].settings.color; // ^^
					context.moveTo(0, 0); // move to the top left
					context.lineTo(points[0].x, points[0].y); // draw to the first point
					var p = 0;
					for (p = 1; p < points.length - 2; p ++){ // loop through almost all of the points, but leave the last 2 alone and we'll code that in by hand
						var xc = (points[p].x + points[p + 1].x) / 2; // math for x offset
						var yc = ((points[p].y + points[p + 1].y) / 2); // ^^ but for y

						context.quadraticCurveTo(points[p].x, points[p].y, xc, yc); // draw the curve to this new point, using the x and y offsets
					// curve through the last two points
					context.quadraticCurveTo(points[p].x, points[p].y, points[p+1].x,points[p+1].y); // draw the last two by hand
					context.lineTo(size.x, 0); // draw a line to the top right so the fill works properly
	this.clear = function(){ // this will come in handy for practical uses
		waves = [];
	loop(); // start the animation