Work

Work

Snippets

Snippets

Graphics

Graphics

Blog

Blog

Email Search

Bubble Burst

By Dónal - May 2017

Mouse control

Multi-layered

Parallax

A demonstration of multi-layered mouse-controlled parallax, with a side of bubble popping.

View Demo

Besides the often over-used parallax scrolling technique in websites and apps, another approach to parallax is that controlled by the movement of the mouse pointer on the X and Y axes within a contained section – a method I’ll refer to as ‘mouse-controlled parallax’. This method can be quite effective with the use of multiple control layers. In this snippet, I will demonstrate this by creating multiple layers, filling each layer with bubbles, and adjusting the layers at different rates of movement to achieve the illusion of parallax. And just for fun you can pop the bubbles too! Check out the demo first.

As so many bubbles would be time consuming to mark up and difficult to maintain, I’ll use JavaScript (with jQuery) to create the HTML as well as the control mouse events. The whole process can be broken into the following steps:

  1. Create layers
  2. Fill layers with bubbles
  3. Style the bubbles
  4. Map mouse movements to layer positions
  5. Create bubble-pop functions

Let’s dive in! (ahem).

1. Create layers

For the desired parallax effect and depth I’ll be creating four layers, each one sitting right over the next, just as in Photoshop. Each layer will have a generic and a unique layer class for styling and control.

// Create layers
$('<div />', { 'class': 'layer layer1'  }).appendTo('body');
$('<div />', { 'class': 'layer layer2'  }).appendTo('body');
$('<div />', { 'class': 'layer layer3'  }).appendTo('body');
$('<div />', { 'class': 'layer layer4'  }).appendTo('body');

2. Fill layers with bubbles

In order to achieve a realistic effect, the abundance of bubbles in each layer must be proportional to its layer’s depth. In other words, the layer closest to us will have a few bubbles, the next layer down will have some more, the next more again, and the last will have the most bubbles. In addition, the apparent size of the bubbles on each layer will vary, again depending on the depth. As there are four layers, there will also be four bubble sizes: large (lg), medium (md), small (sm), and extra small (xs). First, we’ll create some variables to keep the bubble count in proportion:

// Bubble count proportions
var bubbliness = 8; // Recommended between 1 and 10
var xsBubbles = bubbliness*7.5; 
var smBubbles = bubbliness*5; 
var mdBubbles = bubbliness*3; 
var lgBubbles = bubbliness*2;

As you can see from above, the ‘bubbliness’ can change and still keep the counts in proportion. So next thing to do is start creating bubbles. For each layer in turn, we’ll use a for loop with a count defined from before to create the element, append it to its layer, and give it a unique random position on the screen (this is done by assigning the ‘top’ and ‘left’ CSS properties to random values between 0% and 100%).

// Fill layer 1
for(var i = 0; i < xsBubbles; i++) {
var topPos = (Math.random() * 100) + '%';
	var leftPos = (Math.random() * 100) + '%';
	$('<div />', { 'class': 'bubble bubble-xs' }).appendTo('.layer1').css("top", topPos).css("left", leftPos);
}
// Fill layer 2
for(var i = 0; i < smBubbles; i++) {
	var topPos = (Math.random() * 100) + '%';
	var leftPos = (Math.random() * 100) + '%';
	$('<div />', { 'class': 'bubble bubble-sm' }).appendTo('.layer2').css("top", topPos).css("left", leftPos);
}
// Fill layer 3
for(var i = 0; i < mdBubbles; i++) {
	var topPos = (Math.random() * 100) + '%';
	var leftPos = (Math.random() * 100) + '%';
	$('<div />', { 'class': 'bubble bubble-md' }).appendTo('.layer3').css("top", topPos).css("left", leftPos);
}
// Fill layer 4
for(var i = 0; i < lgBubbles; i++) {
	var topPos = (Math.random() * 100) + '%';
	var leftPos = (Math.random() * 100) + '%';
	$('<div />', { 'class': 'bubble bubble-lg' }).appendTo('.layer4').css("top", topPos).css("left", leftPos);
}

3. Style the bubbles

This would be a good time to add some CSS so that we can see the result of the random distribution throughout the layers and get a good idea of how the parallax will behave. First we’ll apply a nice gradient to the background to give an underwater effect. Then we’ll position the layers and apply a fading opacity to add to the depth:

html {
height: 100%;
}
body {
background: rgb(65,208,252);
background: -moz-linear-gradient(top,  rgba(65,208,252,1) 0%, rgba(7,118,255,1) 100%);
background: -webkit-linear-gradient(top,  rgba(65,208,252,1) 0%,rgba(7,118,255,1) 100%);
background: linear-gradient(to bottom,  rgba(65,208,252,1) 0%,rgba(7,118,255,1) 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#41d0fc', endColorstr='#0776ff',GradientType=0 );
overflow: hidden; /* Important for preventing unwanted scrolling */
}
.layer {
position: absolute;
top: 0 ;
left: 0;
width: 100%;
height: 100%;	
pointer-events: none; /* Important for allowing mouse events through layers */
}
.layer1 {
opacity: 0.3;
}
.layer2 {
opacity: 0.5;
}
.layer3 {
opacity: 0.6;
}
.layer4 {
opacity: 1;
}

Next to style the bubbles themselves. A radial white-to-transparent gradient together with a pseudo :after shine effect will do the trick. And finally to set the four different bubble sizes:

.bubble {
position: absolute;
background: -moz-radial-gradient(center, ellipse cover,  rgba(255,255,255,0) 0%, rgba(255,255,255,0) 40%, rgba(255,255,255,1) 100%);
background: -webkit-radial-gradient(center, ellipse cover,  rgba(255,255,255,0) 0%,rgba(255,255,255,0) 40%,rgba(255,255,255,1) 100%);
background: radial-gradient(ellipse at center,  rgba(255,255,255,0) 0%,rgba(255,255,255,0) 40%,rgba(255,255,255,1) 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00ffffff', endColorstr='#ffffff',GradientType=1 );
border-radius: 50%;	
pointer-events: auto;
}
.bubble:after {
content: "";
position: absolute;
background: -moz-radial-gradient(center, ellipse cover,  rgba(255,255,255,1) 0%, rgba(255,255,255,0) 50%, rgba(255,255,255,0) 100%);
background: -webkit-radial-gradient(center, ellipse cover,  rgba(255,255,255,1) 0%,rgba(255,255,255,0) 50%,rgba(255,255,255,0) 100%);
background: radial-gradient(ellipse at center,  rgba(255,255,255,1) 0%,rgba(255,255,255,0) 50%,rgba(255,255,255,0) 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#00ffffff',GradientType=1 );
width: 60%;
height: 60%;
top: 5%;
right: 5%;
border-radius: 50%;	
transform: rotateY(30deg) rotateX(30deg);
opacity: 0.8;
}
.bubble-xs {
width: 24px;
height: 24px;
margin-top: -12px;	
margin-left: -12px;	
}
.bubble-sm {
width: 40px;
height: 40px;	
margin-top: -20px;	
margin-left: -20px;	
}
.bubble-md {
width: 70px;
height: 70px;	
margin-top: -35px;	
margin-left: -35px;	
}
.bubble-lg {
width: 100px;
height: 100px;	
margin-top: -50px;	
margin-left: -50px;	
}

4. Map mouse movements to layer positions

At this point we should be able to see a spread of bubbles in all dimensions, with a different scatter on each page reload. It’s now time to add some dynamic movement to the scene. We’ll do this with jQuery’s ‘mousemove’ method, inside which we’ll track the mouse X and Y coordinates and compare them dynamically to the centre of the screen. We can then use these measurements to adjust the CSS ‘top’ and ‘left’ properties of entire layers. This where the magic happens – by applying carefully varied rates of movement to each layer (less at the back, greater at the front), this will achieve a realistic parallax motion. These ‘resistors’ may need adjustment through trial and error, but here I have applied set fractions of a ‘motionResistance’ variable that will remain in proportion to each layer:

$(window).mousemove(function(event){
 
    // Get centre of layer
    var centerX = $(window).width() / 2;
    var centerY = $(window).height() / 2;
 
    // Get mouse coordinates
    var mouseX = event.clientX;
    var mouseY = event.clientY;
 
    var motionResistance = 3; // Recommended between 1 and 10
 
    var layer1X = (mouseX - centerX) / (motionResistance/0.2);
    var layer1Y = (mouseY - centerY) / (motionResistance/0.2);
 
    var layer2X = (mouseX - centerX) / (motionResistance/0.3);
    var layer2Y = (mouseY - centerY) / (motionResistance/0.3);
 
    var layer3X = (mouseX - centerX) / (motionResistance/0.5);
    var layer3Y = (mouseY - centerY) / (motionResistance/0.5);
 
    var layer4X = (mouseX - centerX) / (motionResistance/0.75);
    var layer4Y = (mouseY - centerY) / (motionResistance/0.75);
 
    $('.layer1').css('left', layer1X).css('top', layer1Y);
    $('.layer2').css('left', layer2X).css('top', layer2Y);
    $('.layer3').css('left', layer3X).css('top', layer3Y);
    $('.layer4').css('left', layer4X).css('top', layer4Y);
 
  });

Test it out by moving the mouse around. Try adjusting the motionResistance variable or an individual layer’s fraction of this to achieve as realistic a result as possible. The greater the resistance the less the bubbles will shift, and the less the resistance the more the bubbles will move with the mouse. As it stands, the bubbles will move along with the direction of the mouse. Alternatively, this can be reversed to give the appearance of a first person view together with the mouse (personally I think this works best). Add this is before the previous function:

motionResistance = motionResistance *= -1; // Reverse direction

5. Create bubble-pop functions

Finally, to pop the bubbles let’s simply add a rule that detects a hover over a bubble and adds a class that contains a CSS animation. In this example, the bubble will just wobble on first hover and then pop and disappear on second hover. We’ll do this by creating a ‘mouseover’ function that checks if the hovered bubble has already wobbled. If it has, add the pop-animation class and then hide it altogether. If it hasn’t, add the wobble-animation class. Simple!

$('.bubble').mouseover( function() {
	if ( $(this).hasClass('bubble-wobble') ) {
		var thisBubble = $(this);
		thisBubble.addClass('bubble-pop');
		setTimeout( function() {
			thisBubble.hide();
		}, 500);
	} else {
		$(this).addClass('bubble-wobble');
	}
});
.bubble-wobble {
-webkit-animation: bubbleWobble 0.5s ease;
-moz-animation: bubbleWobble 0.5s ease;
animation: bubbleWobble 0.5s ease;
background: -moz-radial-gradient(center, ellipse cover,  rgba(255,255,255,0) 0%, rgba(255,255,255,0) 30%, rgba(255,255,255,1) 100%);
background: -webkit-radial-gradient(center, ellipse cover,  rgba(255,255,255,0) 0%,rgba(255,255,255,0) 30%,rgba(255,255,255,1) 100%);
background: radial-gradient(ellipse at center,  rgba(255,255,255,0) 0%,rgba(255,255,255,0) 30%,rgba(255,255,255,1) 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00ffffff', endColorstr='#ffffff',GradientType=1 );
}
@-webkit-keyframes bubbleWobble {
0% {
-webkit-transform: scale(1);
}
20% {
-webkit-transform: scale(0.8);
}
40% {
-webkit-transform: scale(1.1);
}
60% {
-webkit-transform: scale(0.95);
} 
80% {
-webkit-transform: scale(1.03);
} 
100% {
-webkit-transform: scale(1);
} 
}
@-moz-keyframes bubbleWobble {
0% {
-moz-transform: scale(1);
}
20% {
-moz-transform: scale(0.8);
}
40% {
-moz-transform: scale(1.1);
}
60% {
-moz-transform: scale(0.95);
} 
80% {
-moz-transform: scale(1.03);
} 
100% {
-moz-transform: scale(1);
} 
}
@keyframes bubbleWobble {
0% {
transform: scale(1);
}
20% {
transform: scale(0.8);
}
40% {
transform: scale(1.1);
}
60% {
transform: scale(0.95);
} 
80% {
transform: scale(1.03);
} 
100% {
transform: scale(1);
} 
}
 
.bubble-pop {
-webkit-animation: bubblePop 0.4s ease;
-moz-animation: bubblePop 0.4s ease;
animation: bubblePop 0.4s ease;
opacity: 0;
}
@-webkit-keyframes bubblePop {
0% {
-webkit-transform: scale(1);
opacity: 1;
}
20% {
-webkit-transform: scale(0);
opacity: 0.6;
}
100% {
-webkit-transform: scale(0.5);
opacity: 0;
}
}
@-moz-keyframes bubblePop {
0% {
-moz-transform: scale(1);
opacity: 1;
}
20% {
-moz-transform: scale(0);
opacity: 0.6;
}
100% {
-moz-transform: scale(0.5);
opacity: 0;
}
}
@keyframes bubblePop {
0% {
transform: scale(1);
opacity: 1;
}
20% {
transform: scale(0);
opacity: 0.6;
}
100% {
transform: scale(0.5);
opacity: 0;
}
}

That’s it, quite simple really. Above all, it demonstrates how parallax can be achieved with multiple layers controlled by mouse movement rather than scrolling. The bubble-popping also highlights the advantage of having some temptation to move the mouse within the parallax section in order to discover the parallax itself.

View Demo

Share on

Share on Facebook Share on Twitter Share on Google Plus Share on Pinterest

View more

CSS

Google

JavaScript

Lists

Mouse control

Multi-layered

Parallax

Scroll event

Visual feedback