<p class="text">serendipity</p>
<svg id="svg" width="400" height="300" viewbox="0 0 400 300" id="svg">
  <path id="curve" d="M10,150 Q200,150 390,150" 
        fill="none" stroke="#f52e6d" stroke-width="6"/>
</svg>
#svg {
  position: absolute;
  margin-left: -200px;
  margin-top: -150px;
  left: 50%;
  top: 50%;
  z-index: 10;
  cursor: arrow;
}

.text {
  width: 380px;
  height: 260px;
  position: absolute;
  margin-left: -190px;
  margin-top: -150px;
  left: 50%;
  top: 50%;
  font-size: 78px;
  line-height: 228px;
  pointer-events: none;
  z-index: 0;
}
var svgElemnt, path;
var connected, tweening, tween;
var mousePos = {}, svgTop;

function init() {
  svgElement = document.getElementById('svg');
  path = document.getElementById('curve');
  setSVGTop();
  addListeners();
  loop();
}

function setSVGTop() {
  svgTop = svgElement.getBoundingClientRect().top;
}

function addListeners() {
  window.addEventListener('mousemove', function(e) {
    // storing the y position of the mouse - we want the y pos relative to the SVG container so we'll subtract the container top from clientY.
  	mousePos.y = e.clientY - svgTop;
	});
  
  window.addEventListener('resize', setSVGTop);
  
  path.addEventListener('mouseover', function() {
    // if we haven't connected yet and we're not tweening back to center, bgin connection
    if (!connected && !tweening) {
      connected = true;
      svgElement.style.cursor = 'pointer';
    }
	});
}

function updateCurve() {
  var y = mousePos.y;
  y = mousePos.y - (150-mousePos.y)*1.1;
  if (Math.abs(150-y) > 100) {
    connected = false;
    tweening = true;
    svgElement.style.cursor = 'default';
    snapBack(y);
  } else {
    path.setAttribute('d', 'M10,150 Q200,'+y+' 390,150');
  }
}

function snapBack(y) {
  tween = new TWEEN.Tween({ y: y })
    .to({ y: 150 }, 800)
    .easing( TWEEN.Easing.Elastic.Out )
    .onUpdate( function () {
      updatePath(this.y);
    }).onComplete(function() {
      tweening = false;
    }).start();
}

function updatePath(y) {
  // update SVG path control point
  path.setAttribute('d', 'M10,150 Q200,'+y+' 390,150');
}

function loop(time) {
  if (connected) updateCurve();
  TWEEN.update(time);
  requestAnimationFrame(loop);
}

window.onload = init;




Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://s3-us-west-2.amazonaws.com/s.cdpn.io/53148/requestAnimationFrame.js
  2. https://s3-us-west-2.amazonaws.com/s.cdpn.io/53148/tween.min.js