In this post, I go through the steps I took to recreate the background that used to be on the iCloud web site - blurry bubbles!
The above image shows what the iCloud web site used to look like. The background was animated and randomly generated. I really liked this effect and so re-created it myself using Javascript and a HTML Canvas element.
I’ll only post relevant code snippets to illustrate any concepts. For the full code I recommend that you view the project on Github.
To keep the library/package/module/whatever-you-want-to-call-it self contained and to keep from polluting the global namespace, we can define everything in an Immediately-Invoked Function Expression. This simply means we can start with the following:
var GJBubbles = (function () {
// Module implementation goes here
return {}; // Publicly accessible methods and properties go here
})();
Anything we put inside the function will be “private” and anything inside the object that’s returned will be “public”. We’ll put the inner workings inside the private scope and then the public API inside the returned object.
Are these really bubbles? Perhaps I should have called them orbs. Anyway, the bubble object looks like this:
function Bubble(x, y, dx, dy, radius, opacity, blur)
{
// Assign the function parameters to local variables
this.x = x; // etc...
// Draw the circle
this.draw = function (canvas, context) {
// Check the bounds of the bubble
this.checkBounds(canvas);
// Move the bubble
this.x += this.dx;
this.y += this.dy;
// Draw the bubble using a radial gradient
var radgrad = context.createRadialGradient(...);
radgrad.addColorStop(0, 'rgba(255,255,255,' + this.opacity + ')');
radgrad.addColorStop(1, 'rgba(255,255,255,0)');
context.fillStyle = radgrad;
context.fillRect(this.x, this.y, this.size * 2, this.size * 2);
};
// Checks to see if the bubble is out of bounds of the passed in canvas and reverses the direction
this.checkBounds = function (canvas) {
...
}
}
There’s a lot more going on here in the actual code (here), but for simplicity I’ve stripped a few things out such as parameter assignments, validation and animating the bubble in. The main things to focus on are the draw()
and the checkBounds()
functions. draw()
calls checkBounds()
to see if the bubble has reached the edge of the canvas that it’s on and if it has, makes it “bounce” off in a different direction by inverting the values in the movement deltas. It then moves the bubble and draws it in its new location.
Note that the amount the bubble is blurred is proportionate to the bubble’s size (smaller bubbles are more blurry).
Next, we need a function to loop over an array of bubbles and draw them onto a canvas. Notice how we recursively pass the same function to window.requestAnimationFrame so that the bubbles get redrawn each frame.
function draw(bubbles, canvas)
{
var context = canvas.getContext('2d');
context.clearRect(0,0, canvas.width, canvas.height);
for (var i = 0; i < bubbles.length; i++) {
bubbles[i].draw(canvas, context);
}
var raf = window.requestAnimationFrame(function() {
draw(bubbles, canvas);
});
}
I wanted this to be able to be included on a web page and then easily added to a containing element, so in the return statement I have an init()
function that takes an ID of a container and a list of options. The options are values like how many bubbles you’d like and the minimum and maximum values you want each bubble property to be (for example, the size, speed and opacity).
return {
options: {
// How many bubbles, max and min speed, size etc.
},
init: function(container_id, options) {
// Get the options, overwriting defaults
// Get the container, create a canvas and append it as a child
var container = document.getElementById(container_id);
var canvas = document.createElement("canvas");
container.appendChild(canvas);
// Set the canvas size to be the same as the container
var bubbles = [];
// Create the bubbles using setInterval to stagger their creation
var self = this;
var interval = setInterval(function() {
var radius = randomBetween(min_radius, max_radius);
// The next two options make bubbles that are smaller appear blurrier and more transparent. Bigger ones are more in focus and opaque.
var blur = 0.8 - (radius - (canvas.width * (self.options.min_radius / 100))) / ((canvas.width * (self.options.max_radius / 100)) - (canvas.width * (self.options.min_radius / 100)));
var opacity = blur * self.options.max_opacity;
if (opacity < self.options.min_opacity) {
opacity = self.options.min_opacity;
}
bubbles.push(new Bubble(
randomBetween(0, canvas.width),
randomBetween(0, canvas.height),
randomBetween(-self.options.max_speed, self.options.max_speed),
randomBetween(-self.options.max_speed, self.options.max_speed),
radius,
opacity,
blur
));
if (bubbles.length == self.options.bubble_count) {
clearInterval(interval);
}
}, 50);
draw(bubbles, canvas);
}
};
To use the module, add the following to a web page:
<div id="my-div"></div>
<script src="src/GJBubbles.js"></script>
<script>
GJBubbles.init('my-div', {bubble_count: 10});
</script>
As mentioned before, to keep this post from getting too code-heavy, I’ve stripped bits out, so be sure to check out the repository on GitHub.
comments powered by Disqus