'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); });

!