<div class="demo">
  <div class="demo__top">
    <svg class="demo__top-svgBg" viewBox="0 0 366 256">
      <g class="svgBg__objects">
        <path class="svgBg__bg svgBg__bg1" fill="#86D7DB" d="M0,143 88,107 224,153 348,109 366,123 366,256 0,256z" />
        <path class="svgBg__bg svgBg__bg2" fill="#3C929A" d="M0,156 106,136 272,172 342,124 366,144 366,256 0,256z" />
        <path class="svgBg__bg svgBg__bg3" fill="#416175" d="M0,170 62,160 235,148 305,145 366,153 366,256 0,256z" />
        <g class="svgBg__tree svgBg__tree-1 m--left" data-id="1">
          <path class="svgBg__tree-leafs svgBg__tree-part" fill="#389296" d="M54,127 C77,127 62,95 54,63 C46,95 31,127 54,127" />
          <path class="svgBg__tree-trunk svgBg__tree-part" d="M56,147 Q55,115 54,83 Q53,115 52,146" />
        </g>
        <g class="svgBg__tree svgBg__tree-2 m--left" data-id="2">
          <path class="svgBg__tree-leafs svgBg__tree-part" fill="#389296" d="M67,124 C90,124 75,92 67,59 C59,92 44,124 67,124" />
          <path class="svgBg__tree-trunk svgBg__tree-part" d="M69,144 Q68,112 67,80 Q66,112 65,143" />
        </g>
        <g class="svgBg__tree svgBg__tree-3 m--right" data-id="3">
          <path class="svgBg__tree-leafs svgBg__tree-part" fill="#389296" d="M264,129 C287,129 272,97 264,64 C256,97 241,129 264,129" />
          <path class="svgBg__tree-trunk svgBg__tree-part" d="M266,149 Q265,117 264,85 Q263,117 262,148" />
        </g>
        <g class="svgBg__tree svgBg__tree-4 m--right" data-id="4">
          <path class="svgBg__tree-leafs svgBg__tree-part" fill="#207277" d="M287,128 C310,128 295,96 287,63 C279,96 264,128 287,128" />
          <path class="svgBg__tree-trunk svgBg__tree-part" d="M289,148 Q288,116 287,84 Q286,116 285,147" />
        </g>
        <g class="svgBg__tree svgBg__tree-5 m--right" data-id="5">
          <path class="svgBg__tree-leafs svgBg__tree-part" fill="#389296" d="M313,128 C336,128 321,96 313,63 C305,96 290,128 313,128" />
          <path class="svgBg__tree-trunk svgBg__tree-part" d="M315,148 Q314,116 313,84 Q312,116 311,147" />
        </g>
      </g>
    </svg>
  </div>
  <div class="demo__body">
    <div class="plane-cont">
      <div class="plane-rotater">
        <div class="plane">
          <svg class="plane-svg" viewBox="0 0 28 26">
            <path class="plane-svg__path" fill="#fff" d="M0,0 28,13 0,26 0,13 20,13 0,7z" />
          </svg>
        </div>
      </div>
    </div>
    <div class="pull-down">Pull down</div>
    <div class="items">
      <div class="item item-3">
        <a href="https://twitter.com/NikolayTalanov" target="_blank" class="icon-box item__icon">
          <span class="icon-box__inner m--left">
            <i class="fa fa-twitter"></i>
          </span>
          <span class="icon-box__inner m--right">
            <i class="fa fa-twitter"></i>
          </span>
        </a>
        <a href="https://twitter.com/NikolayTalanov" target="_blank" class="item__name">My Twitter</a>
      </div>
      <div class="item item-2">
        <a href="https://codepen.io/suez/public/" target="_blank" class="icon-box item__icon">
          <span class="icon-box__inner m--left">
            <i class="fa fa-codepen"></i>
          </span>
          <span class="icon-box__inner m--right">
            <i class="fa fa-codepen"></i>
          </span>
        </a>
        <a href="https://codepen.io/suez/public/" target="_blank" class="item__name">My other demos</a>
      </div>
      <div class="item item-1">
        <div class="item__icon m--img">
          <img src="//s3-us-west-2.amazonaws.com/s.cdpn.io/142996/profile/profile-80_5.jpg" />
        </div>
        <span class="item__name">Feed the cat!</span>
      </div>
    </div>
  </div>
</div>
*, *:before, *:after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}
html, body {
  font-size: 62.5%;
  
  @media (max-width: 768px) {
    font-size: 50%;
  }
}
body {
  background: #EDEFF2;
}

$demoW: 36.6rem;
$demoH: 47rem;
$topH: 18.6rem;
$topMaxH: 25.6rem;
$bodyH: $demoH - $topH;
$planeAnimTime: 3.5s;

.demo {
  position: absolute;
  left: 50%;
  top: 50%;
  margin-left: $demoW/-2;
  margin-top: $demoH/-2;
  width: $demoW;
  height: $demoH;
  background: #FFFFFF;
  border-radius: 1.2rem;
  box-shadow: 0 2rem 2rem rgba(0,0,0,0.15);
  overflow: hidden;
  
  &__top {
    position: relative;
    height: $topH;
    background: linear-gradient(#7BCECA, #82D3CB);
    overflow: hidden;
  }
  
  &__body {
    position: relative;
    min-height: $bodyH*2;
    padding-top: 5rem;
    will-change: transform;
  }
}

.pull-down {
  position: absolute;
  left: 0;
  top: 1rem;
  width: 100%;
  font-size: 2rem;
  text-align: center;
  color: rgba(#545C67, 0.6);
  user-select: none;
  pointer-events: none;
  
  &:before,
  &:after {
    content: "";
    position: absolute;
    top: 0;
    width: 1rem;
    height: 1rem;
    border: 1px solid rgba(#545C67, 0.6);
    border-left: none;
    border-top: none;
    transform: rotate(45deg);
    animation: arrowAnim 1.5s infinite;
  }
  
  &:before {
    left: 11rem;
  }
  &:after {
    left: 25rem;
  }
}

@keyframes arrowAnim {
  to {
    transform: translateY(1.3rem) rotate(45deg);
    opacity: 0;
  }
}

.items {
  position: relative;
  
  &.padded {
    transition: padding 0.3s;
    padding-top: 8rem;
  }
}

.item {
  height: 8rem;
  padding: 2rem 2.5rem;
  user-select: none;
  transition: opacity 0.3s;
  
  &.absPos {
    position: absolute;
    left: 0;
    top: 0;
  }
  
  &.hidden {
    opacity: 0;
  }
  
  &__icon {
    display: inline-block;
    vertical-align: top;
    width: 4rem;
    height: 4rem;
    margin-right: 2rem;
    border-radius: 50%;
    
    &.animated {
      animation: animateIcon 0.6s forwards;
    }
    
    &.m--img {
      
      img {
        width: 100%;
      }
    }
  }
  
  &__name {
    font-size: 2rem;
    line-height: 4rem;
    color: #545C67;
  }
}

@keyframes animateIcon {
  20% {
    transform: scaleY(0.7);
  }
  40% {
    transform: scaleY(0.9);
  }
  60% {
    transform: scaleY(0.6);
  }
  80% {
    transform: scaleY(1.1);
  }
  100% {
    transform: scaleY(1);
  }
}

.plane-cont {
  position: absolute;
  left: 1.7rem;
  top: -2.8rem;
  width: 5.6rem;
  height: 5.6rem;
  background: #5DB2DF;
  border-radius: 50%;
  box-shadow: 0 0.3rem 0.3rem rgba(0,0,0,0.3);
}

.plane-rotater {
  position: absolute;
  left: 50%;
  top: 50%;
  margin-left: -1rem;
  margin-top: -1.3rem;
  width: 2.8rem;
  height: 2.6rem;
}

.plane {
  
  &.fly {
    animation: planeFly $planeAnimTime forwards;
  }
}

@keyframes planeFly {
  28% {
    transform: translate(55rem, 13rem) rotate(20deg) scale(0.7);
  }
  35% {
    transform: translate(45rem, -8rem) rotate(-160deg) scale(0.5);
  }
  85% {
    transform: translate(-15rem, -4rem) rotate(-180deg) scale(0.7);
  }
  90% {
    transform: translate(-15rem, 0) rotate(0deg);
  }
  100% {
    transform: rotate(0deg);
  }
}

.svgBg {
  
  &__bg {
    transform-origin: 183px 256px;
  }
  
  &__tree {
    
    &-trunk {
      fill: #1E5E65;
    }
    
    &-part {
      transform-origin: inherit;
    }
    
    &-1 {
      transform-origin: 54px 147px;
      opacity: 0.7;
      
      .svgBg__tree-part {
        transform: scale(0.35, 0.44);
      }
    }
    
    &-2 {
      transform-origin: 67px 144px;
      opacity: 0.7;
      
      .svgBg__tree-part {
        transform: scale(0.56, 0.65);
      }
    }
    
    &-3 {
      transform-origin: 264px 149px;
      
      .svgBg__tree-part {
        transform: scale(0.58, 0.65);
      }
    }
    
    &-4 {
      transform-origin: 287px 148px;
      
      .svgBg__tree-part {
        transform: scale(0.8, 1);
      }
    }
    
    &-5 {
      transform-origin: 313px 148px;
      
      .svgBg__tree-part {
        transform: scale(0.5, 0.61);
      }
    }
  }
}

/*
font awesome icon slicing effect from https://codepen.io/suez/pen/KpwEeg
*/
$boxSize: 4rem;
$fontSize: $boxSize * 0.6;
$xDiff: $boxSize / 20;
$yDiff: $boxSize / 10;
$animTime: 0.5s;
// change this if you need another angle (now works between 0 and 45 deg)
$deg: 30deg;
$degCoef: $deg / 45deg;
$sliceLeft: (1 - $degCoef) / 2 * 100%;
$sliceX: $xDiff + (1 - $degCoef) / 2 * $xDiff + 0.1rem;
.icon-box {
  position: relative;
  display: inline-block;
  vertical-align: top;
  width: $boxSize;
  height: $boxSize;
  background: #0c0c0c;
  overflow: hidden;
  font-size: 0;
  text-decoration: none;
  border-radius: 50%;
  
  &:before {
    content: "";
    position: absolute;
    left: $sliceLeft;
    top: 0;
    margin-left: -1px;
    width: 2px;
    height: 0.6rem;
    background: #fff;
    transition: transform $animTime;
    transform: rotate($deg * -1) translate($sliceX, -1rem);
  }
  
  &:hover:before {
    animation: slice $animTime;
  }
  
  &__inner {
    position: relative;
    display: inline-block;
    vertical-align: top;
    overflow: hidden;
    width: 50%;
    height: 100%;
    transform: rotate($deg * -1);
    font-size: $fontSize;
    color: #fff;
    
    .fa {
      position: absolute;
      top: 50%;
      transform: translate(-50%, -50%) rotate($deg);
    }
    
    &.m--left {
      transform-origin: 100% 50%;

      .fa {
        left: 100%;
      }
    }

    &.m--right {
      transform-origin: 0 50%;
      transition: transform $animTime;
      
      .icon-box:hover & {
        transition: transform $animTime $animTime*0.2;
        transform: rotate($deg * -1) translate($xDiff, $yDiff);
      }

      .fa {
        left: 0;
      }
    }
  }
}

@keyframes slice {
  to {
    transform: rotate(-$deg) translate($sliceX, $boxSize*1.5 + 1rem);
  }
}
View Compiled
'use strict';

window.requestAnimFrame = (function() {
  return window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.oRequestAnimationFrame ||
    window.msRequestAnimationFrame ||
    function(callback) {
    window.setTimeout(callback, 1000 / 60);
  };
})();

$(document).ready(function() {

  var $top = $(".demo__top");
  var $body = $(".demo__body");
  var $bg1 = $(".svgBg__bg1");
  var $bg2 = $(".svgBg__bg2");
  var $bg3 = $(".svgBg__bg3");
  // jQuery have problems with getting svg elements attrs, so I'm using vanillaJS
  var $trees = [].slice.call(document.querySelectorAll(".svgBg__tree"));
  var $treeParts = [].slice.call(document.querySelectorAll(".svgBg__tree-part"));
  var $leftTrees = $(".svgBg__tree.m--left");
  var $rightTrees = $(".svgBg__tree.m--right");
  var $planeRotater = $(".plane-rotater");
  var $plane = $(".plane");
  var isDesktop = window.matchMedia("(min-width: 769px)").matches;
  var topH = (isDesktop) ? 186 : 149;
  var bg1change, bg2change, bg3change;
  var bg1max = (isDesktop) ? 10 : 8;
  var bg2max = (isDesktop) ? 22 : 18;
  var bg3max = (isDesktop) ? 44 : 35;
  var pullDeltaY;
  var maxPullDeltaY = (isDesktop) ? 70 : 56;
  var treesData = {};
  var treeMaxX = (isDesktop) ? 18 : 14;
  var treeMaxCoef = treeMaxX / maxPullDeltaY;
  var treeChange;
  var planeMaxDeg = -45; // defines maximum plane rotation deg during pull event
  var planeMaxCoef = planeMaxDeg / maxPullDeltaY;
  var planeChange;
  var frame = 1000 / 60; // 60 frames per second
  // duration for release animation for all elements, except flying plane
  var releaseTime = 900;
  var animating = false;
  var planeAnimTime = 3500; // this value must be synced with SASS $planeAnimTime
  
  /* You can find these easing functions on this site
  http://timotheegroleau.com/Flash/experiments/easing_function_generator.htm
  Also, you can customize them with generator,
  like i customized this elasticBig easing, to heavily shake these trees
  */
  var easings = {
    elastic: function(t,b,c,d) {
      var ts = (t/=d)*t;
      var tc = ts*t;
      return b+c*(33*tc*ts + -106*ts*ts + 126*tc + -67*ts + 15*t);
    },
    elasticBig: function(t,b,c,d) {
      var ts = (t/=d)*t;
      var tc = ts*t;
      return b+c*(21*tc*ts + -150*ts*ts + 250*tc + -150*ts + 30*t);
    },
    inCubic: function(t,b,c,d) {
      var tc = (t/=d)*t*t;
      return b+c*(tc);
    }
  };
  
  /* store clones in object */
  var cloneCounter = 1;
  var $items = $(".items");
  var clones = {
    clone1: $(".item-1").clone(),
    clone2: $(".item-2").clone(),
    clone3: $(".item-3").clone()
  };
  
  /* Applies class with padding transition, which shifts content down,
  then it's prepends clone with 0 opacity and absolute position (0,0).
  Then this clone fades in and padding class being removed from $items and
  absolute position removed from inserted clone
  */
  function insertNewClone() {
    var $clone = clones["clone"+cloneCounter];
    $clone.addClass("absPos hidden");
    $items.prepend($clone).addClass("padded");
    $clone.css("top");
    $clone.removeClass("hidden");
    $clone.find(".item__icon").addClass("animated");
    cloneCounter++;
    if (cloneCounter > 3) cloneCounter = 1;
    setTimeout(function() {
      $items.removeClass("padded");
      $clone.removeClass("absPos");
    }, 300);
  };
  
  /* This looks messy, but basically I'm storing tree parts paths D attributes as arrays
  and X&Y coordinates of middle points.
  */
  function storeTreeCoords() {
    var treeId, treeObj, trunkTop, leafsTop;
    
    $trees.forEach(function($tree) {
      treeId = $tree.getAttribute("data-id");
      treesData["tree"+treeId] = {};
      treeObj = treesData["tree"+treeId];
      treeObj.isRight = $tree.classList.contains("m--right");
      treeObj.$treeTrunk = $tree.querySelector(".svgBg__tree-trunk");
      treeObj.$treeLeafs = $tree.querySelector(".svgBg__tree-leafs");
      treeObj.trunkInitArrD = treeObj.$treeTrunk.getAttribute("d").split(" ");
      treeObj.leafsInitArrD = treeObj.$treeLeafs.getAttribute("d").split(" ");
      trunkTop = treeObj.trunkInitArrD[2];
      leafsTop = treeObj.leafsInitArrD[3];
      treeObj.trunkInitX = +trunkTop.split(",")[0];
      treeObj.leafsInitX = +leafsTop.split(",")[0];
      treeObj.trunkInitY = +trunkTop.split(",")[1];
      treeObj.leafsInitY = +leafsTop.split(",")[1];
    });
  };
  
  storeTreeCoords();
  
  /* Each tree consists of two parts - trunk and leafs. 
  Both of these parts created with two quadratic bezier curves (left and right sides).
  Trunk created with C curve, leafs with Q curve. Here you can find good explanation about them:
  http://tutorials.jenkov.com/svg/path-element.html
  Basically, I'm just changing middle point X coordinate of each part
  and it's affects both curves, so this looks like I'm magically tilt these trees
  */
  function tiltTrees(x) {
    var treeId, treeObj, trunkArr, leafsArr, changeX;

    $trees.forEach(function($tree) {
      treeId = $tree.getAttribute("data-id");
      treeObj = treesData["tree"+treeId];
      trunkArr = treeObj.trunkInitArrD.slice();
      leafsArr = treeObj.leafsInitArrD.slice();
      changeX = (treeObj.isRight) ? x : -x;
      
      trunkArr[2] = (treeObj.trunkInitX + changeX/2) + "," + treeObj.trunkInitY;
      leafsArr[3] = (treeObj.leafsInitX + changeX) + "," + treeObj.leafsInitY;

      treeObj.$treeTrunk.setAttribute("d", trunkArr.join(" "));
      treeObj.$treeLeafs.setAttribute("d", leafsArr.join(" "));
    });
  };
  
  /* Moving mountains and tree <g> elements with transform translateY
  transform-origin's hardcoded for each element in css and scales with viewBox
  */
  function moveBgs() {
    $bg1.css({"-webkit-transform": "translate3d(0,"+bg1change+"px, 0)",
              "transform": "translate3d(0,"+bg1change+"px, 0)"});
    $bg2.css({"-webkit-transform": "translate3d(0,"+bg2change+"px, 0)",
              "transform": "translate3d(0,"+bg2change+"px, 0)"});
    $bg3.css({"-webkit-transform": "translate3d(0,"+bg3change+"px, 0)",
              "transform": "translate3d(0,"+bg3change+"px, 0)"});
    $leftTrees.css({"-webkit-transform": "translate3d(0,"+bg2change+"px, 0)",
                    "transform": "translate3d(0,"+bg2change+"px, 0)"});
    $rightTrees.css({"-webkit-transform": "translate3d(0,"+bg3change+"px, 0)",
                     "transform": "translate3d(0,"+bg3change+"px, 0)"});
  };
  
  function checkMaxBgValues() {
    if (bg1change > bg1max) bg1change = bg1max;
    if (bg2change > bg2max) bg2change = bg2max;
    if (bg3change > bg3max) bg3change = bg3max;
  };
  
  // applies changes for all elements
  function applyChanges(topY) {
    $top.css("height", topH + topY + "px");
    moveBgs();
    tiltTrees(treeChange);
    $planeRotater.css({"-webkit-transform": "rotate("+planeChange+"deg)",
                       "transform": "rotate("+planeChange+"deg)"});
  };
  
  /* calculates numbers for applyChanges function, when
  you are using mousemove/touchmove pull event
  */
  function pullChange(y) {
    if (y < 0) y = 0;
    if (y > maxPullDeltaY) y = maxPullDeltaY;
    bg1change = bg2change = bg3change = y;
    checkMaxBgValues();
    treeChange = y * treeMaxCoef;
    planeChange = y * planeMaxCoef;
    
    applyChanges(y);
  };
  
  /* calculates numbers for applyChanges function, when
  release event is fired
  */
  function releaseChange(props) {
    bg1change = bg2change = bg3change = props.bgY;
    checkMaxBgValues();
    treeChange = props.treeVal * treeMaxCoef;
    planeChange = props.planeDeg * planeMaxCoef;
    
    applyChanges(props.topY);
  };
  
  function release() {
    // number of frames, which you need to animate with requestAnimationFrame
    var steps = Math.floor(releaseTime / frame);
    var curStep = 0;
    var topY, bgY, treeVal, planeDeg;
    var y = pullDeltaY;
    if (y > maxPullDeltaY) y = maxPullDeltaY;
    var releasePlane = y >= maxPullDeltaY/2;
    animating = true; // prevents from pull event during animation
    // if you pulled more than 1/2 of maxPullDeltaY - starts the plane flight animation
    if (releasePlane) {
      $plane.addClass("fly"); // adds class to plane with keyframes animation
      setTimeout(function() {
        // when animation is over, allow pull events, remove keyframes class and add new clone
        animating = false;
        $plane.removeClass("fly");
        insertNewClone();
      }, planeAnimTime);
    }
    
    /* this function fires each available frame,
    until animation will be over (curStep > steps)
    */
    function animate() {
      curStep++;
      // applies different easings for different groups of elements
      topY = easings.elastic(curStep, y, 0 - y, steps);
      bgY = easings.elastic(curStep, y, 0 - y, steps);
      treeVal = easings.elasticBig(curStep, y, 0 - y, steps);
      planeDeg = easings.inCubic(curStep, y, 0 - y, steps);
      
      releaseChange({topY: topY, bgY: bgY, treeVal: treeVal, planeDeg: planeDeg});
      
      if (curStep > steps) {
        pullDeltaY = 0;
        // if pulled less than 1/2 of maxPullDeltaY - allow pull event earlier
        if (!releasePlane) animating = false;
        return;
      }
      requestAnimFrame(animate);
    }
    animate();
  };
  
  /* On mousedown/touchstart, attaches mousemove/touchmove events
  for dynamic pull change events. When mouseup/touchend event fired -
  runs release function and removes move/end events
  */
  $(document).on("mousedown touchstart", ".demo__body", function(e) {
    if (animating) return; // prevents from pulling during the release animation
    var startY =  e.pageY || e.originalEvent.touches[0].pageY;
    
    $(document).on("mousemove touchmove", function(e) {
      var y = e.pageY || e.originalEvent.touches[0].pageY;
      pullDeltaY = (y - startY) / 1.5; // slightly slow pull event for better experience
      if (!pullDeltaY) return; // prevents from rapid click events
      pullChange(pullDeltaY);
    });

    $(document).on("mouseup touchend", function() {
      $(document).off("mousemove touchmove mouseup touchend");
      if (!pullDeltaY) return; // prevents from rapid click events
      release();
    });
  });
  
  // source - http://davidwalsh.name/javascript-debounce-function
  function debounce(func, wait, immediate) {
    var timeout;
    return function() {
      var context = this, args = arguments;
      var later = function() {
        timeout = null;
        if (!immediate) func.apply(context, args);
      };
      var callNow = immediate && !timeout;
      clearTimeout(timeout);
      timeout = setTimeout(later, wait);
      if (callNow) func.apply(context, args);
    };
  };
  
  /* redifine max values for desktop/mobile
  all other things scales with rem units and viewBox
  */
  var resizeFn = debounce(function() {
    isDesktop = window.matchMedia("(min-width: 769px)").matches;
    topH = (isDesktop) ? 186 : 149;
    bg1max = (isDesktop) ? 10 : 8;
    bg2max = (isDesktop) ? 22 : 18;
    bg3max = (isDesktop) ? 44 : 35;
    maxPullDeltaY = (isDesktop) ? 70 : 56;
    treeMaxX = (isDesktop) ? 18 : 14;
  }, 100);
  
  $(window).on("resize", resizeFn);
  

});

External CSS

  1. //maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css

External JavaScript

  1. //cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js