(function () {
'use strict';
var Animation = function (opts) {
var internal = {
boundsCheckInterval: 100,
animations: [],
scroll: {
top: 0,
bottom: 0
},
animatedScroll: {
duration: 800
}
};
var fn = {
// Public
animate: function animate (elem, callback) {
var animationObj = fn.newAnimationObj(elem, callback);
internal.animations.push(animationObj);
return animationObj;
},
start: function start () {
fn.updateScrollPosition();
window.requestAnimationFrame(fn.runAnimations);
document.body.onresize = function () {
fn.checkElemBounds();
};
},
scrollTo: function scrollTo (elem) {
var animatedScroll = internal.animatedScroll;
var start = fn.scrollTop();
var to = elem.offsetTop;
var change = to - start;
var duration = animatedScroll.duration;
var startTime = Date.now();
var animateScroll = function () {
var elapsedTime = Date.now() - startTime;
var newScrollTop = ease(elapsedTime, start, change, duration);
fn.scrollTop(newScrollTop);
if (elapsedTime < duration) {
requestAnimationFrame(animateScroll);
}
};
var ease = function (t, b, c, d) {
t /= d/2;
if (t < 1) { return c / 2 * t * t + b; }
t--;
return 0 - c / 2 * (t * (t - 2) - 1) + b;
};
animateScroll();
},
// Private
newAnimationObj: function newAnimationObj (elem, callback) {
var animationObj = {
elem: elem,
callback: callback,
bounds: {},
inView: false,
observer: null,
stop: function stop () {
fn.stop(this);
}
};
animationObj.observer = new MutationObserver(function () {
fn.updateAnimationObjBounds(animationObj);
});
animationObj.observer.observe(elem, {
childList: true,
characterData: true,
attributes: true,
subtree: true
});
fn.updateAnimationObjBounds(animationObj);
return animationObj;
},
runAnimations: function runAnimations () {
var animations = internal.animations;
fn.updateScrollPosition();
animations.forEach(function (animationObj) {
if (fn.isInView(animationObj)) {
animationObj.callback({
scroll: internal.scroll,
bounds: animationObj.bounds
});
}
});
window.requestAnimationFrame(fn.runAnimations);
},
checkElemBounds: function checkElemBounds () {
var animations = internal.animations;
animations.forEach(fn.updateAnimationObjBounds);
},
updateAnimationObjBounds: function updateAnimationObjBounds (animationObj) {
var scroll = internal.scroll;
var elemBounds = animationObj.elem.getBoundingClientRect();
var calculatedBounds = {
top: elemBounds.top + scroll.top,
right: elemBounds.right,
bottom: elemBounds.bottom + scroll.top,
left: elemBounds.left,
height: elemBounds.height,
width: elemBounds.width,
x: elemBounds.x,
y: elemBounds.y + scroll.top
};
animationObj.bounds = calculatedBounds;
},
isInView: function isInView (animationObj) {
var scroll = internal.scroll;
return animationObj.bounds.top < scroll.bottom && animationObj.bounds.bottom > scroll.top;
},
stop: function stop (animationObj) {
var animations = internal.animations;
var animIndex = animations.indexOf(animationObj);
animations.splice(animIndex, 1);
},
//Utils
updateScrollPosition: function updateScrollPosition () {
var scrollTop = fn.scrollTop();
internal.scroll = {
top: scrollTop,
bottom: scrollTop + document.body.offsetHeight,
height: document.body.offsetHeight
};
},
scrollTop: function scrollTop (setVal) {
if ( setVal === void 0 ) setVal = null;
var doc = document.documentElement;
if (!setVal) {
return (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
} else {
doc.scrollTop = setVal;
}
}
};
return {
animate: fn.animate,
start: fn.start,
scrollTo: fn.scrollTo
};
};
var Nav = function (ref) {
var animation = ref.animation;
var navEl = document.querySelector('nav');
var navControlEl = navEl.querySelector('.nav-control');
var navPanelEl = navEl.querySelector('.nav-panel');
var linkEls = document.querySelectorAll('a[href]');
var state = {
open: false
};
var scrollToSection = function (sectionHash) {
var linkTargetEl = document.querySelector(sectionHash);
animation.scrollTo(linkTargetEl);
};
navControlEl.addEventListener('click', function () {
state.open = !state.open;
if (state.open) {
navEl.classList.add('open');
} else {
navEl.classList.remove('open');
}
});
linkEls.forEach(function (linkEl) {
if (!linkEl.hash) { return; }
linkEl.addEventListener('click', function (evnt) {
scrollToSection(linkEl.hash);
});
});
if (window.location.hash) {
scrollToSection(window.location.hash);
setTimeout(function () {
scrollToSection(window.location.hash);
}, 500);
}
};
var Hero = function (opts) {
var heroSectionEl = document.querySelector('section.hero');
var titlesEl = heroSectionEl.querySelector('.titles');
var setSelection = function () {
var selection = window.getSelection();
var newRange = new Range();
var mainTitleH1 = heroSectionEl.querySelector('.main-title h1');
var h1TextNode = mainTitleH1.firstChild;
newRange.setStart(h1TextNode, 0);
newRange.setEnd(h1TextNode, 4);
selection.removeAllRanges();
selection.addRange(newRange);
};
setTimeout(setSelection, 300);
};
var History = function (ref) {
var animation = ref.animation;
var historySectionEl = document.querySelector('.history');
var historyAnimation = animation.animate(historySectionEl, function (ref) {
var scroll = ref.scroll;
var bounds = ref.bounds;
if (scroll.top >= bounds.top - scroll.height * 0.3) {
historySectionEl.classList.add('show-icon');
historyAnimation.stop();
}
});
};
var Demo = function (opts) {
var animation = opts.animation;
var demoSectionEl = document.querySelector('section.demo');
var editablePaneEl = demoSectionEl.querySelector('.demo-pane[contenteditable]');
var codeEl = demoSectionEl.querySelector('.demo-pane code');
var openTag = function (elem) {
var tagString = '';
tagString += '';
tagString += '<';
tagString += elem.tagName.toLowerCase();
for (var i = 0; i < elem.attributes.length; i++) {
var attribute = elem.attributes[i];
tagString += ' ' + attribute.name + '="' + attribute.value + '"';
}
tagString += '>';
tagString += '';
return tagString;
};
var closeTag = function (elem) {
var tagString = '';
tagString += '';
tagString += '</';
tagString += elem.tagName.toLowerCase();
tagString += '>';
tagString += '';
return tagString;
};
var elemToCodeStr = function (elem, indent, parentIsBlock) {
if (elem.nodeType === Node.ELEMENT_NODE && !elem.childNodes.length) {
return openTag(elem) + '';
}
if (!elem.textContent.trim().length) { return ''; }
var elemHTML = '';
var tagName = elem.tagName;
var isBlockTag = ['H1', 'H2', 'P', 'UL', 'OL', 'LI', 'BLOCKQUOTE'].indexOf(tagName) > -1;
var inlineContent = ['H1', 'H2', 'UL', 'OL', 'A', 'SPAN', 'B', 'I'].indexOf(tagName) > -1;
if (isBlockTag) {
elemHTML += '';
}
if (elem.nodeType !== Node.ELEMENT_NODE) {
elemHTML += elem.textContent.replace(/\s+/g, ' ');
} else {
elemHTML += openTag(elem);
if (!inlineContent) {
elemHTML += '';
}
for (var i = 0; i < elem.childNodes.length; i++) {
elemHTML += elemToCodeStr(elem.childNodes[i], indent + 1, isBlockTag);
}
if (!inlineContent) {
elemHTML += '';
}
elemHTML += closeTag(elem);
}
if (isBlockTag) {
elemHTML += '';
}
return elemHTML;
};
var updateCodeDisplay = function () {
var codeDisplayHTML = '';
for (var i = 0; i < editablePaneEl.childNodes.length; i++) {
codeDisplayHTML += elemToCodeStr(editablePaneEl.childNodes[i], 0);
}
codeEl.innerHTML = codeDisplayHTML;
};
var editObserver = new MutationObserver(updateCodeDisplay);
editObserver.observe(editablePaneEl, {
childList: true,
characterData: true,
attributes: true,
subtree: true
});
updateCodeDisplay();
var textNodes = [];
var editablePaneHTML = editablePaneEl.innerHTML;
var textNode;
editablePaneEl.style.height = editablePaneEl.offsetHeight + 'px';
var h1Clone = editablePaneEl.querySelector('h1').cloneNode(true);
editablePaneEl.innerHTML = '';
editablePaneEl.appendChild(h1Clone);
editablePaneEl.classList.add('typing-in');
var textWalker = document.createTreeWalker(editablePaneEl.querySelector('h1'), NodeFilter.SHOW_TEXT, null, false);
while (textNode = textWalker.nextNode()) {
textNodes.push({
charArray: Array.from(textNode.textContent),
node: textNode,
delay: textNodes.length * 100
});
textNode.textContent = '';
}
var skippedFrames = 0;
var typeInEffect = function () {
if (skippedFrames < 2) {
skippedFrames++;
requestAnimationFrame(typeInEffect);
return;
}
skippedFrames = 0;
var textNode = textNodes[0];
textNode.node.textContent += textNode.charArray.shift();
if (!textNode.charArray.length) {
textNodes.shift();
}
if (textNodes.length) {
requestAnimationFrame(typeInEffect);
} else {
setTimeout(finishTypeInEffect, 200);
}
};
var finishTypeInEffect = function () {
editablePaneEl.innerHTML = editablePaneHTML;
editablePaneEl.style.height = 'auto';
requestAnimationFrame(function () {
editablePaneEl.classList.remove('typing-in');
});
};
var startTypeInEffect = function (ref) {
var scroll = ref.scroll;
var bounds = ref.bounds;
console.log(scroll.top, bounds.top - scroll.height / 2 );
if (scroll.top >= bounds.top - scroll.height / 2) {
typeInEffect();
typeInAnimation.stop();
}
};
var typeInAnimation = animation.animate(demoSectionEl, startTypeInEffect);
};
var Install = function (opts) {
// Nothing to see here. Move along.
};
// Utils
var animation = Animation();
animation.start();
[
Nav,
Hero,
History,
Demo,
Install
].forEach(function (Module) {
Module({
animation: animation
});
});
}());