<div id="container" class="container"></div>
html,
body,
.container {
height: 100%;
margin: 0;
padding: 0;
width: 100%;
}
body {
background-color: #616161;
}
.container {
overflow: hidden;
perspective: 50px;
}
.bubble {
animation: 1s linear infinite alternate brightnessChanging;
border-radius: 50%;
box-shadow: inset 0 0 5px rgba(255, 255, 255, .25);
height: 50px;
margin: -25px 0 0 -25px;
overflow: hidden;
position: absolute;
width: 50px;
transform: translateZ(0) rotate(-20deg);
transition: transform;
}
.bubble::before {
background-color: rgba(255, 255, 255, 0.3);
border-radius: 50%;
content: '';
height: 4px;
margin: 4px 0 0px 17px;
position: absolute;
width: 15px;
}
.bubble::after {
border-radius: 50%;
box-shadow: inset 0 -8px 8px rgba(255, 255, 255, .15);
content: '';
height: 80%;
position: absolute;
margin: 15% 0 0 6%;
width: 90%;
}
@keyframes brightnessChanging {
from {
opacity: 1;
}
to {
opacity: .75;
}
}
/* jshint esnext:true */
class Bubble {
constructor(params) {
this.isMoving = false;
this._shakeTimeout = null;
this.opts = Object.assign({
template: () => '<div></div>',
maxMove: 20,
maxDelay: 2,
minDelay: 0.5,
maxTime: 3,
minTime: 0.5,
additionalTransform: 'rotate(-20deg)'
}, params || {});
this.create();
this.shake();
}
create() {
let tempCont = this._tempCont;
if (!tempCont) {
tempCont = document.createElement('div');
this._tempCont = tempCont;
}
tempCont.innerHTML = this.opts.template();
this.elem = tempCont.children[0];
}
shake() {
clearTimeout(this._shakeTimeout);
let duration = 0;
if (!this.isMoving) {
const {
maxMove, maxTime, minTime
} = this.opts;
const x = this._getRandom(-maxMove, maxMove, 0);
const y = this._getRandom(-maxMove, maxMove, 0);
const z = this._getRandom(-maxMove, maxMove, 0);
duration = this._getRandom(minTime, maxTime);
this._setPosition({
x, y, z
}, duration);
}
const {
minDelay, maxDelay
} = this.opts;
const nextShakeDelay = Math.max(this._getRandom(minDelay, maxDelay),
duration) * 1000;
this._shakeTimeout = setTimeout(this.shake.bind(this), nextShakeDelay);
}
_setPosition(coords, duration) {
const {
x, y, z
} = coords;
const transformVal =
`translate3d(${x}px, ${y}px, ${z}px) ${this.opts.additionalTransform}`;
const style = this.elem.style;
style.transitionDuration = `${duration}s`;
window.requestAnimationFrame(() => style.transform = transformVal);
}
/**
* Returns a random number from a range
* @param {number} minVal
* @param {number} maxVal
* @param {number} [fixed=3]
* @returns {number}
*/
_getRandom(minVal, maxVal, fixed = 3) {
let val = Math.random() * (maxVal - minVal) + minVal;
let r = Math.pow(10, Math.abs(fixed));
return Math.round(val * r) / r;
}
}
// bubbles
// ================
const bubblesTotal = 101;
const bubblesCont = document.querySelector('#container');
const bubbles = [];
for (let i = 0; i < bubblesTotal; i++) {
let bubble = new Bubble({
template: bubbleTemplate
});
let elem = bubble.elem;
elem.style.top = Math.round(Math.random() * 100) + '%';
elem.style.left = Math.round(Math.random() * 100) + '%';
bubblesCont.appendChild(elem);
bubbles.push(bubble);
}
window.bubbles = bubbles;
/**
* @param {object} params
* @returns (string) html string
*/
function bubbleTemplate(params) {
return `<div class="bubble"></div>`;
}
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.