<div id="app">
  <div class="bubble-wrapper">
    <div ref="slackBubble" class="bubble">
      <img class="bubble-image"
           :src="currentLogo" />
    </div>
    <div ref="slackBubblePulse" class="bubble-pulse"></div>
  </div>
  
  <button @click="randomiseLogo">Random Logo</button>
</div>
.bubble-wrapper {
  position: relative;
}

.bubble {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid white;
  background: #272727;
  border-radius: 50%;
  height: 100px;
  width: 100px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
}

.bubble-pulse {
  position: absolute;
  z-index: 1;
  top: 50%;
  left: 50%;
  margin-top: -60px;
  margin-left: -60px;
  background: rgba(255, 255, 255, 0.1);
  height: 120px;
  width: 120px;
  border-radius: 50%;
  opacity: 0;
  transform: scale(0);
}

.bubble-image {
  width: 50%;
}

/* Global Styles */
body {
  background: #1a1a1a;
  padding-top: 80px;
  display: flex;
  align-items: center;
  justify-content: center;
}

button {
  margin-top: 40px;
  position: relative;
  z-index: 50;
}
new Vue({
  el: "#app",

  data() {
    return {
      timeline: null,
      logos: [
        'https://s3.ap-southeast-2.amazonaws.com/daily-fire-assets/slack-white.svg',
        'https://s3.ap-southeast-2.amazonaws.com/daily-fire-assets/discord-white.svg',
        'https://s3.ap-southeast-2.amazonaws.com/daily-fire-assets/messenger-white.png',
      ],
      currentLogo: ''
    };
  },
  
  methods: {
     randomiseLogo() {
       const logosToSample = this.logos.filter(logo => logo !== this.currentLogo)
       this.currentLogo = logosToSample[Math.floor(Math.random() * logosToSample.length)]
     }
  },

  mounted() {
    this.randomiseLogo()
    const { slackBubble, slackBubblePulse } = this.$refs;
    this.timeline = new TimelineLite({
      onComplete: () => this.timeline.restart()
    });

    this.timeline.to(slackBubble, 0.4, {
      scale: 0.8,
      rotation: 16,
      ease: Back.easeOut.config(1.7)
    });
    this.timeline.to(
      slackBubblePulse,
      0.8,
      {
        scale: 0.9,
        opacity: 1
      },
      "-=0.6"
    );
    this.timeline.to(slackBubble, 1.2, {
      scale: 1,
      rotation: "-=16",
      ease: Elastic.easeOut.config(2.5, 0.5)
    });
    this.timeline.to(
      slackBubblePulse,
      1.1,
      {
        scale: 3,
        opacity: 0,
        ease: Expo.easeOut
      },
      "-=1.2"
    );
  }
});
View Compiled
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/gsap/2.0.2/TweenLite.min.js
  3. https://cdnjs.cloudflare.com/ajax/libs/gsap/2.0.2/TimelineLite.min.js
  4. https://cdnjs.cloudflare.com/ajax/libs/gsap/2.0.2/easing/EasePack.min.js
  5. https://cdnjs.cloudflare.com/ajax/libs/gsap/2.0.2/plugins/CSSPlugin.min.js