<svg viewBox="0 0 800 600" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice">
  <defs>
      <filter id="blur" x="-100%" y="-100%" width="250%" height="250%">
        <feGaussianBlur stdDeviation="8" result="coloredBlur" />
        <feOffset dx="0" dy="28" result="offsetblur"></feOffset>
             <feFlood id="glowAlpha" flood-color="#0C8BE9" flood-opacity="0.42"></feFlood>
        <feComposite in2="offsetblur" operator="in"></feComposite>
        <feMerge>
          <feMergeNode/>          
          <feMergeNode in="SourceGraphic"></feMergeNode>
        </feMerge>
      </filter>     
   <circle class="splash" cx="0" cy="0" r="0"/>
    <radialGradient id="bubbleGrad" cx="33.59" cy="29.75" r="40" gradientTransform="translate(9.47 -7.08) rotate(16.42)" gradientUnits="userSpaceOnUse">
      <stop offset="0" stop-color="#fff" stop-opacity="0.7"/>
      <stop offset="0.3" stop-color="#fcfefe" stop-opacity="0.69"/>
      <stop offset="0.46" stop-color="#f4fafc" stop-opacity="0.67"/>
      <stop offset="0.58" stop-color="#e5f3f9" stop-opacity="0.64"/>
      <stop offset="0.69" stop-color="#d0eaf4" stop-opacity="0.59"/>
      <stop offset="0.79" stop-color="#b5ddee" stop-opacity="0.52"/>
      <stop offset="0.88" stop-color="#94cee6" stop-opacity="0.44"/>
      <stop offset="0.97" stop-color="#6dbcdd" stop-opacity="0.34"/>
      <stop offset="1" stop-color="#5cb4d9" stop-opacity="0.3"/>
    </radialGradient>
        
<g id="bubbleGroup" >
        <circle cx="29.28" cy="29.28" r="30"  fill="url(#bubbleGrad)"/>
        <ellipse cx="15.78" cy="14.95" rx="7.93" ry="4.6" transform="translate(-5.82 17.34) rotate(-49.75)" fill="#fff" opacity="0.32"/>
      </g> 
     
  </defs>
<g id="bubbleToggle" filter="url(#blur)">
<rect id="bg" x="306" y="249.87" width="188" height="100.27" rx="50.13" ry="50.13" fill="#3fa9f5"/> 
 <g id="splashContainer"/>
     <use id="bubbleL" xlink:href="#bubbleGroup" x="327" y="271" opacity="0.31"/>
     <use id="bubbleR" xlink:href="#bubbleGroup" x="417" y="271" opacity="0"/>
      
   </g> 
 <rect id="mainHit" width="100%" height="100%" fill="transparent"/>
</svg>
body {
 background-color: #F5F5F0;
 overflow: hidden;
 text-align:center;
  display: flex;
  align-items: center;
  justify-content: center; 
}

body,
html {
 height: 100%;
 width: 100%;
 margin: 0;
 padding: 0;
}

svg {
 width: 100%;
 height: 100%;
 visibility: hidden;
 
}
class App {
 
 
 select = (s) => {return document.querySelector(s);}
 selectAll = (s) => {return document.querySelectorAll(s);} 
 mainTl = new TimelineMax();
 isOn = false;
 particleArr = [];
 bubbleToggle = this.select('#bubbleToggle');
 mainBubble = this.select('#mainBubble');
 mainHit = this.select('#mainHit');
 bubbleL = this.select('#bubbleL');
 bubbleR = this.select('#bubbleR');
 oldBubble = this.bubbleR;
 currentBubble = this.bubbleL;
 splashContainer = this.select('#splashContainer');
 splashColorArr = ['#C9E5FA', '#C0E1F9', '#8BC8F0', '#BEE0F9'];
 
  constructor(){

   TweenMax.staggerTo([this.bubbleL, this.bubbleR], 0,{
    cycle:{
     svgOrigin:['357 300','445 300']
    }
   },0)
   this.createSplash();
   this.mainHit.addEventListener('click', this.clickBubble)

  }
 
  clickBubble = e => {
   this.isOn = !this.isOn;
   this.oldBubble = (this.isOn) ? this.bubbleL : this.bubbleR; 
   this.currentBubble = (this.isOn) ? this.bubbleR : this.bubbleL; 
   
   var bubbleTl = new TimelineMax().timeScale(0.5);
   
   bubbleTl.staggerTo([this.bubbleL, this.bubbleR], 0.5,{
     cycle:{
      alpha:(this.isOn) ? [0, 1] : [0.31,0],
      scale:(this.isOn) ? [1.1, 1] : [1,1.1],
      duration:(this.isOn) ? [0, 0] : [0,0],
      ease:(this.isOn) ? Expo.easeOut : Sine.easeIn
     },
    
   },0)
   
  .fromTo(this.currentBubble,1, {    
    scaleX:0.86  
   },{
    scaleX:1,
    ease:Elastic.easeOut.config(0.8,0.16)
   },0)
  .fromTo(this.currentBubble, 0.6, {
    scaleY:0.7
   },{    
    scaleY:1,
    ease:Elastic.easeOut.config(0.6,0.35)
  },'-=1')  
   
   if(this.isOn) {return }
   this.playSplash()
  }
 
  createSplash = e =>{
   const splash = this.select('.splash');
   let num = 50;
   while(--num > -1){
    let clone = splash.cloneNode(true);
    this.particleArr.push(this.splashContainer.appendChild(clone));
    this.initSplash(clone);

   }

 }
 
 initSplash = clone =>{
   TweenMax.set(clone, {
    x:this.randomBetween(Number(this.oldBubble.getAttribute('x'))+0, Number(this.oldBubble.getAttribute('x')) + 45),
    y:this.randomBetween(Number(this.oldBubble.getAttribute('y'))+0, Number(this.oldBubble.getAttribute('y')) + 60),
    alpha:0,
    scale:1,
    fill:this.splashColorArr[this.randomBetween(0, this.splashColorArr.length-1) ],
    attr:{
     r:this.randomBetween(15, 30)/5
    },
     transformOrigin:'50% 50%'
   })  
 }
 
  playSplash (){
   var tl = new TimelineMax();   
   tl.staggerTo(this.particleArr, 0,{
    cycle: {
     scale: (i, t) => {
      this.initSplash(t);
      return Math.random()
     }
    },
     alpha:1
     })
   .staggerTo(this.particleArr, 1, {
    cycle:{
     physics2D:()=>{
      return {velocity: this.randomBetween(10, 30),
       angle: this.randomBetween(90, -135),
       gravity: this.randomBetween(-150, -50)}
     },
       duration: () => {
        return Math.random() * 3
       }
     
    },
    scale:0,
     alpha:0,
    ease:Sine.easeOut
   })

  }
 
 randomBetween = (min, max) => {
  return Math.floor(Math.random() * (max - min + 1) + min);
} 
 
  get timeline():boolean {
    return this.mainTl;
  }
 
}


TweenMax.set('svg', {
  visibility: 'visible'
})

new App();

View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/gsap/2.0.2/TweenMax.min.js
  2. https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/Physics2DPlugin.min.js