main.js 11.4 KB
'use strict';

window.requestAnimFrame = (function() {
  return window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.oRequestAnimationFrame ||
    window.msRequestAnimationFrame ||
    function(callback) {
    window.setTimeout(callback, 1000 / 60);
  };
})();
//var hh = document.documentElement.clientHeight;
$(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 hh = document.documentElement.clientHeight;
  //var hhh = ($('.demo__top').height())?$('.demo__top').height():parseInt($('.demo__top').css('height'));
  //alert(hhh);
  var topH = (isDesktop) ? 186 : hh*0.3;//149;188
  //var topH = (isDesktop) ? 186 : 188;//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)
  after this clone fades in and padding class 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) {
  	
  	//console.log(hh);
  	$top.css("height", topH + topY + "px");
  	console.log(topH+"   "+topY);
    //$top.css("height", '30%');
    
    moveBgs();
    tiltTrees(treeChange);
    $planeRotater.css({"-webkit-transform": "rotate("+planeChange+"deg)",
                       "transform": "rotate("+planeChange+"deg)"});
                       //$top.css("height", '30%');
  };
  
  /* 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() {
    // numbers 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");
      //$top.css("height", '30%');
      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 : hh*0.3;//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);
  

});