elevator.js
Version:
Elevator.js fixes those awkward "scroll to top" moments the old fashioned way.
273 lines (223 loc) • 6.58 kB
JavaScript
/*!
* Elevator.js
*
* MIT licensed
* Copyright (C) 2015 Tim Holman, http://tholman.com
*/
/*********************************************
* Elevator.js
*********************************************/
var Elevator = function(options) {
"use strict";
// Elements
var body = null;
// Scroll vars
var animation = null;
var duration = null; // ms
var customDuration = false;
var startTime = null;
var startPosition = null;
var endPosition = 0;
var targetElement = null;
var verticalPadding = null;
var elevating = false;
var startCallback;
var mainAudio;
var endAudio;
var endCallback;
var that = this;
/**
* Utils
*/
// Thanks Mr Penner - http://robertpenner.com/easing/
function easeInOutQuad(t, b, c, d) {
t /= d / 2;
if (t < 1) return c / 2 * t * t + b;
t--;
return -c / 2 * (t * (t - 2) - 1) + b;
}
function extendParameters(options, defaults) {
for (var option in defaults) {
var t =
options[option] === undefined && typeof option !== "function";
if (t) {
options[option] = defaults[option];
}
}
return options;
}
function getVerticalOffset(element) {
var verticalOffset = 0;
while (element) {
verticalOffset += element.offsetTop || 0;
element = element.offsetParent;
}
if (verticalPadding) {
verticalOffset = verticalOffset - verticalPadding;
}
return verticalOffset;
}
/**
* Main
*/
// Time is passed through requestAnimationFrame, what a world!
function animateLoop(time) {
if (!startTime) {
startTime = time;
}
var timeSoFar = time - startTime;
var easedPosition = easeInOutQuad(
timeSoFar,
startPosition,
endPosition - startPosition,
duration
);
window.scrollTo(0, easedPosition);
if (timeSoFar < duration) {
animation = requestAnimationFrame(animateLoop);
} else {
animationFinished();
}
}
// ELEVATE!
// /
// ____
// .' '=====<0
// |======|
// |======|
// [IIIIII[\--()
// |_______|
// C O O O D
// C O O O D
// C O O O D
// C__O__O__O__D
// [_____________]
this.elevate = function() {
if (elevating) {
return;
}
elevating = true;
startPosition = document.documentElement.scrollTop || body.scrollTop;
updateEndPosition();
// No custom duration set, so we travel at pixels per millisecond. (0.75px per ms)
if (!customDuration) {
duration = Math.abs(endPosition - startPosition) * 1.5;
}
requestAnimationFrame(animateLoop);
// Start music!
if (mainAudio) {
mainAudio.play();
}
if (startCallback) {
startCallback();
}
};
function browserMeetsRequirements() {
return (
window.requestAnimationFrame &&
window.Audio &&
window.addEventListener
);
}
function resetPositions() {
startTime = null;
startPosition = null;
elevating = false;
}
function updateEndPosition() {
if (targetElement) {
endPosition = getVerticalOffset(targetElement);
}
}
function animationFinished() {
resetPositions();
// Stop music!
if (mainAudio) {
mainAudio.pause();
mainAudio.currentTime = 0;
}
if (endAudio) {
endAudio.play();
}
if (endCallback) {
endCallback();
}
}
function onWindowBlur() {
// If animating, go straight to the top. And play no more music.
if (elevating) {
cancelAnimationFrame(animation);
resetPositions();
if (mainAudio) {
mainAudio.pause();
mainAudio.currentTime = 0;
}
updateEndPosition();
window.scrollTo(0, endPosition);
}
}
function bindElevateToElement(element) {
if (element.addEventListener) {
element.addEventListener("click", that.elevate, false);
} else {
// Older browsers
element.attachEvent("onclick", function() {
updateEndPosition();
document.documentElement.scrollTop = endPosition;
document.body.scrollTop = endPosition;
window.scroll(0, endPosition);
});
}
}
function init(_options) {
// Bind to element click event, if need be.
body = document.body;
var defaults = {
duration: undefined,
mainAudio: false,
endAudio: false,
preloadAudio: true,
loopAudio: true,
startCallback: null,
endCallback: null
};
_options = extendParameters(_options, defaults);
if (_options.element) {
bindElevateToElement(_options.element);
}
// Take the stairs instead
if (!browserMeetsRequirements()) {
return;
}
if (_options.duration) {
customDuration = true;
duration = _options.duration;
}
if (_options.targetElement) {
targetElement = _options.targetElement;
}
if (_options.verticalPadding) {
verticalPadding = _options.verticalPadding;
}
window.addEventListener("blur", onWindowBlur, false);
if (_options.mainAudio) {
mainAudio = new Audio(_options.mainAudio);
mainAudio.setAttribute("preload", _options.preloadAudio);
mainAudio.setAttribute("loop", _options.loopAudio);
}
if (_options.endAudio) {
endAudio = new Audio(_options.endAudio);
endAudio.setAttribute("preload", "true");
}
if (_options.endCallback) {
endCallback = _options.endCallback;
}
if (_options.startCallback) {
startCallback = _options.startCallback;
}
}
init(options);
};
if (typeof module !== "undefined" && module.exports) {
module.exports = Elevator;
}