<!--

Follow me on
Dribbble: https://dribbble.com/supahfunk
Twitter: https://twitter.com/supahfunk
Codepen: https://codepen.io/supah/

-->

<div id="ghost" class="ghost">
  <div class="ghost__head">
    <div class="ghost__eyes"></div>
    <div class="ghost__mouth"></div>
  </div>
  <div class="ghost__tail">
    <div class="ghost__rip"></div>
  </div>
</div>


<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <defs>
    <filter id="goo">
      <feGaussianBlur
        in="SourceGraphic"
        stdDeviation="10"
        result="ghost-blur" />
      <feColorMatrix
        in="ghost-blur"
        mode="matrix"
        values="
                1 0 0 0 0
                0 1 0 0 0
                0 0 1 0 0
                0 0 0 16 -7"
        result="ghost-gooey" />
    </filter>
  </defs>
</svg>
$bg-color: #161616;

html,
body {
  height: 100vh;
  margin: 0;
  padding: 0;
  overflow: hidden;
  background: $bg-color;
  pointer-events: none;
}

.ghost {
  position: absolute;
  z-index: 1;
  // filter:url('#goo');
  transform-origin: center;
  width: 90px;
  margin: 20px 0 0 -45px;
  
  &__eyes,
  &__mouth {
    position: absolute;
    z-index: 1;
    width: 15px;
    height: 15px;
    top: 34px;
    left: 50%;
    transform: translate(-50%);
    border-radius: 50px;
    background: $bg-color;
    margin-left: -20px;
    transform-origin: center;
  }
  
  &__eyes {
    box-shadow: 40px 0 0 $bg-color;
  }
  
  &__mouth {
    margin: 0;
    top: 60px;
    transform: scale(0);
    border-radius: 20px 20px 12px 12px;
    width: 20px;
    trasform-origin: center bottom;
    overflow: hidden;
  }
  
  &__tail {
    position: absolute;
    z-index: -1;
    top: 82px;
    height: 55px;
    width: 100%;
    filter: url(#goo);
    
    &:before {
      content: '';
      background: #fff;
      position: absolute;
      bottom: 35px;
      left: 0;
      height: 100px;
      width: 100%;
      border-radius: 40px 40px 5px 5px;
    }
  }
  
  &__rip {
    width: 15px;
    height: 28px;
    background: #fff;
    position: absolute;
    top: 15px;
    left: 0;
    box-shadow: -62px 0 0 #fff, -31px 0 0 #fff, 31px 0 0 #fff, 62px 0 0 #fff, 93px 0 0 #fff;
    border-radius: 50%;
    animation: ghost-rips 1.2s linear infinite;
  }
}

@keyframes ghost-rips {
  0% {
    left: 0;
    top: 12px;
  }
  50%{
    left: 31px;
    top: 20px;
  }
  100%{
    left: 62px;
    top: 12px;
  }
}
View Compiled
/*--------------------
Get Mouse
--------------------*/
let mouse = { x: window.innerWidth / 2, y: window.innerHeight / 2, dir: '' };
let clicked = false;
const getMouse = (e) => {
  mouse = {
    x: e.clientX || e.pageX || e.touches[0].pageX || 0 || window.innerWidth / 2,
    y: e.clientY || e.pageY || e.touches[0].pageY || 0 || window.innerHeight / 2,
    dir: (getMouse.x > e.clientX) ? 'left' : 'right'
  }
};
['mousemove', 'touchstart', 'touchmove'].forEach(e => {
  window.addEventListener(e, getMouse);
});
window.addEventListener('mousedown', (e) => {
  e.preventDefault();
  clicked = true;
});
window.addEventListener('mouseup', () => {
  clicked = false;
});


/*--------------------
Ghost Follow
--------------------*/
class GhostFollow {
  constructor (options) {
    Object.assign(this, options);
    
    this.el = document.querySelector('#ghost');
    this.mouth = document.querySelector('.ghost__mouth');
    this.eyes = document.querySelector('.ghost__eyes');
    this.pos = {
      x: 0,
      y: 0
    }
  }
  
  follow() {
    this.distX = mouse.x - this.pos.x;
    this.distY = mouse.y - this.pos.y;
    
    this.velX = this.distX / 8;
    this.velY = this.distY / 8;
    
    this.pos.x += this.distX / 10;
    this.pos.y += this.distY / 10;
    
    this.skewX = map(this.velX, 0, 100, 0, -50);
    this.scaleY = map(this.velY, 0, 100, 1, 2.0);
    this.scaleEyeX = map(Math.abs(this.velX), 0, 100, 1, 1.2);
    this.scaleEyeY = map(Math.abs(this.velX * 2), 0, 100, 1, 0.1);
    this.scaleMouth = Math.min(Math.max(map(Math.abs(this.velX * 1.5), 0, 100, 0, 10), map(Math.abs(this.velY * 1.2), 0, 100, 0, 5)), 2);
    
    if (clicked) {
      this.scaleEyeY = .4;
      this.scaleMouth = -this.scaleMouth;
    }
    
    this.el.style.transform = 'translate(' + this.pos.x + 'px, ' + this.pos.y + 'px) scale(.7) skew(' + this.skewX + 'deg) rotate(' + -this.skewX + 'deg) scaleY(' + this.scaleY + ')';
    this.eyes.style.transform = 'translateX(-50%) scale(' + this.scaleEyeX + ',' + this.scaleEyeY + ')';
    this.mouth.style.transform = 'translate(' + (-this.skewX*.5-10) + 'px) scale(' + this.scaleMouth + ')';
  }
}


/*--------------------
Map
--------------------*/
function map (num, in_min, in_max, out_min, out_max) {
  return (num - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}


/*--------------------
Init
--------------------*/
const cursor = new GhostFollow();


/*--------------------
Render
--------------------*/
const render = () => {
  requestAnimationFrame(render);
  cursor.follow();
}
render();
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.