<div class="phone">
  <svg width="360" height="480" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <defs>
      <filter id="goo" filterUnits="userSpaceOnUse" x="130" y="0" width="100" height="100">
        <feGaussianBlur in="SourceGraphic" stdDeviation="11" result="blur" />
        <feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 19 -7" result="contrast" />
        <feComposite in="SourceGraphic" in2="contrast" operator="atop"/>
      </filter>
    </defs>
    <path id="card" d="M0,0 H360 V480 H0"/>
    <g filter="url(#goo)">
      <use xlink:href="#card"/>
      <circle id="circle" cx="180" cy="50" r="20"/>  
    </g>
    <g transform="translate(180, 50) scale(1, 1) rotate(90)">
      <path id="progress"/>
    </g>
  </svg>
  <div class="content">
    <ul>
      <li>
        <span class="image"></span>
        <div class="text">
          <span class="headline"></span>
          <span class="paragraph"></span>
          <span class="paragraph"></span>
        </div>
      </li>
      <li>
        <span class="image"></span>
        <div class="text">
          <span class="headline"></span>
          <span class="paragraph"></span>
          <span class="paragraph"></span>
        </div>
      </li>
      <li>
        <span class="image"></span>
        <div class="text">
          <span class="headline"></span>
          <span class="paragraph"></span>
          <span class="paragraph"></span>
        </div>
      </li>
      <li>
        <span class="image"></span>
        <div class="text">
          <span class="headline"></span>
          <span class="paragraph"></span>
          <span class="paragraph"></span>
        </div>
      </li>
      <li>
        <span class="image"></span>
        <div class="text">
          <span class="headline"></span>
          <span class="paragraph"></span>
          <span class="paragraph"></span>
        </div>
      </li>
    </ul>
  </div>
</div>
html,
body {
  width: 100%;
  height: 100%;
}

body {
  display: flex;
  align-items: center;
  justify-content: center;
  user-select: none;
}

.phone {
  position: relative;
  overflow: hidden;
  width: 360px;
  height: 480px;
  background: darken(lightblue, 10%);
}

#card,
#circle {
  fill: lighten(lightblue, 10%);
}

#progress {
  fill: none;
  stroke: lighten(lightblue, 10%);
  stroke-width: 3px;
  transform: scale(1, 1);
  opacity: 1;
  transition: none;
  &.animated {
    transform: scale(1.5, 1.5);
    opacity: 0;
    stroke-width: 0px;
    transition: all .35s ease-in;
  }
}

#circle {
  transform: translate(0, 100px);
  transition: none;
  &.animated {
    transform: translate(0, 0);
    transition: all .25s .05s ease-out;
  }
}

.content {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  opacity: 1;
}

ul {
  margin: 0;
  padding: 0;
}

li {
  display: flex;
  align-items: center;
  justify-content: center;
  border-bottom: 1px solid rgba(255, 255, 255, .25);
  margin-bottom: -1px;
  &:last-child {
    border-bottom: 0;
  }
}

.image {
  width: 4em;
  height: 4em;
  border-radius: 3px;
  margin: 1em;
  background: rgba(255, 255, 255, .5);
}

.text {
  flex: 1;
  padding-right: 1em;
}

.headline {
  display: block;
  margin: .5em 0;
  height: 1em;
  width: 35%;
  border-radius: 3px;
  background: rgba(255, 255, 255, .5);
}

.paragraph {
  display: block;
  margin: .5em 0;
  height: 1em;
  border-radius: 3px;
  background: rgba(255, 255, 255, .35);
  &:nth-child(2n-1) {
    width: 90%;
  }
}
const easing = {
  easeInCubic: t => t ** 3,
  easeOutCubic: t => (--t) * t ** 2 + 1,
  easeInOutCubic: t => t < 0.5 ? 4 * t ** 3 : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
};

let flag;
const $phone = document.querySelector('.phone');
const $content = document.querySelector('.content');
const $card = document.querySelector('#card');
const $circle = document.querySelector('#circle');
const $progress = document.querySelector('#progress');
let trigger;
let release;

const getClientY = e => {
  return parseInt(e.clientY || e.changedTouches[0].clientY, 10);
};

const dragStartListener = e => {
  const y = getClientY(e);
  const edge = $phone.offsetTop;
  if (y > edge && y < edge + 44) {
    resetAnimation();
    flag = true;
    trigger = y + 100;
    release = y + 200;
  }
};

const dragListener = e => {
  if (flag) {
    const y = getClientY(e);
    const size = 100 - (trigger - y);
    setCardPath(size, size);
    setContentStyle(size, 1 - (size / 100));
    if (y >= trigger) {
      setCardPath(Math.min(size, 100), size);
      if (y >= release) {
        flag = false;
        removeEventListeners();
        startBounceAnimation();
        $circle.classList.add('animated');
        setTimeout(() => startProgressAnimation(), 500);
      }
    }
  }
};

const dragEndListener = e => {
  if (flag) {
    flag = false;
    startCloseAnimation(100 - (trigger - getClientY(e)));
  }
};

const addEventListeners = () => {
  document.addEventListener('touchstart', dragStartListener);
  document.addEventListener('mousedown', dragStartListener);

  document.addEventListener('touchmove', dragListener);
  document.addEventListener('mousemove', dragListener);

  document.addEventListener('touchend', dragEndListener);
  document.addEventListener('mouseup', dragEndListener);
};

const removeEventListeners = () => {
  document.removeEventListener('touchstart', dragStartListener);
  document.removeEventListener('mousedown', dragStartListener);

  document.removeEventListener('touchmove', dragListener);
  document.removeEventListener('mousemove', dragListener);

  document.removeEventListener('touchend', dragEndListener);
  document.removeEventListener('mouseup', dragEndListener);
};

const resetAnimation = () => {
  setCardPath(0, 0);
  setContentStyle(0, 1);
  setProgressPath(0);
  $circle.classList.remove('animated');
  $progress.classList.remove('animated');
};

const setContentStyle = (y, opacity) => {
  $content.style.top = `${y}px`;
  $content.style.opacity = opacity;
}

const setCardPath = (y1, y2) => {
  var d = "M360,480 H0 V" + y1 + " Q180," + y2 + " 360," + y1;
  $card.setAttribute('d', d);
};

const setProgressPath = percent => {
  const x = 25 * Math.cos(percent * 6.283185);
  const y = 25 * Math.sin(percent * 6.283185);
  const largeArcFlag = percent <= 0.5 ? 0 : 1;
  const d = "M25,0 A25,25 0 " + largeArcFlag + " 1 " + x + "," + y;
  $progress.setAttribute('d', d);
};

const startBounceAnimation = () => {
  let start;
  const duration = 1250;

  const animation = timestamp => {
    if (!start) {
      start = timestamp;
    }

    const progress = timestamp - start;
    const amplitude = 100 - easing.easeOutCubic(progress / duration) * 100;
    const time = 3 * (progress / duration);
    const y = amplitude * Math.cos(6.283185 * time);
    setCardPath(100, 100 + y);

    if (progress < duration) {
      requestAnimationFrame(animation);
    }
  };

  requestAnimationFrame(animation);
}

const startProgressAnimation = () => {
  let start;
  const duration = 1000;

  const animation = timestamp => {
    if (!start) {
      start = timestamp;
    }

    const progress = timestamp - start;
    const percent = easing.easeInOutCubic(progress / duration);
    setProgressPath(percent);

    if (progress < duration) {
      requestAnimationFrame(animation);
    } else {
      $card.classList.add('animated');
      $progress.classList.add('animated');
      window.setTimeout(() => startCloseAnimation(), 250);
    }
  };

  requestAnimationFrame(animation);
}

const startCloseAnimation = (position = 100) => {
  let start;
  const duration = 1000;

  const animation = timestamp => {
    if (!start) {
      start = timestamp;
    }

    const progress = timestamp - start;
    const y = position - easing.easeInOutCubic(progress / duration) * position;
    setCardPath(y, y);
    setContentStyle(y, 1 - (y / 100));

    if (progress < duration) {
      requestAnimationFrame(animation);
    } else {
      addEventListeners();
    }
  };

  requestAnimationFrame(animation);
}

addEventListeners();
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.