mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
343 lines
9.7 KiB
JavaScript
343 lines
9.7 KiB
JavaScript
/*!
|
|
* headroom.js v0.7.0 - Give your page some headroom. Hide your header until you need it
|
|
* Copyright (c) 2014 Nick Williams - http://wicky.nillia.ms/headroom.js
|
|
* License: MIT
|
|
*/
|
|
|
|
define(['dom', 'layoutManager', 'browser', 'css!./headroom'], function (dom, layoutManager, browser) {
|
|
|
|
'use strict';
|
|
|
|
/* exported features */
|
|
|
|
var requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
|
|
|
|
/**
|
|
* Handles debouncing of events via requestAnimationFrame
|
|
* @see http://www.html5rocks.com/en/tutorials/speed/animations/
|
|
* @param {Function} callback The callback to handle whichever event
|
|
*/
|
|
function Debouncer(callback) {
|
|
this.callback = callback;
|
|
this.ticking = false;
|
|
}
|
|
Debouncer.prototype = {
|
|
constructor: Debouncer,
|
|
|
|
/**
|
|
* dispatches the event to the supplied callback
|
|
* @private
|
|
*/
|
|
update: function () {
|
|
if (this.callback) {
|
|
this.callback();
|
|
}
|
|
this.ticking = false;
|
|
},
|
|
|
|
/**
|
|
* Attach this as the event listeners
|
|
*/
|
|
handleEvent: function () {
|
|
if (!this.ticking) {
|
|
requestAnimationFrame(this.rafCallback || (this.rafCallback = this.update.bind(this)));
|
|
this.ticking = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
function onHeadroomClearedExternally() {
|
|
this.state = null;
|
|
}
|
|
|
|
/**
|
|
* UI enhancement for fixed headers.
|
|
* Hides header when scrolling down
|
|
* Shows header when scrolling up
|
|
* @constructor
|
|
* @param {DOMElement} elem the header element
|
|
* @param {Object} options options for the widget
|
|
*/
|
|
function Headroom(elems, options) {
|
|
options = Object.assign(Headroom.options, options || {});
|
|
|
|
this.lastKnownScrollY = 0;
|
|
this.elems = elems;
|
|
|
|
this.scroller = options.scroller;
|
|
|
|
this.debouncer = onScroll.bind(this);
|
|
this.offset = options.offset;
|
|
this.initialised = false;
|
|
|
|
this.initialClass = options.initialClass;
|
|
this.unPinnedClass = options.unPinnedClass;
|
|
this.pinnedClass = options.pinnedClass;
|
|
|
|
this.state = 'clear';
|
|
|
|
this.options = {
|
|
offset: 0,
|
|
scroller: window,
|
|
initialClass: 'headroom',
|
|
unPinnedClass: 'headroom--unpinned',
|
|
pinnedClass: 'headroom--pinned'
|
|
};
|
|
|
|
this.add = function (elem) {
|
|
|
|
if (browser.supportsCssAnimation()) {
|
|
elem.classList.add(this.initialClass);
|
|
elem.addEventListener('clearheadroom', onHeadroomClearedExternally.bind(this));
|
|
this.elems.push(elem);
|
|
}
|
|
};
|
|
|
|
this.remove = function (elem) {
|
|
|
|
elem.classList.remove(this.unPinnedClass);
|
|
elem.classList.remove(this.initialClass);
|
|
elem.classList.remove(this.pinnedClass);
|
|
|
|
var i = this.elems.indexOf(elem);
|
|
if (i !== -1) {
|
|
this.elems.splice(i, 1);
|
|
}
|
|
};
|
|
|
|
this.pause = function () {
|
|
this.paused = true;
|
|
};
|
|
|
|
this.resume = function () {
|
|
this.paused = false;
|
|
};
|
|
|
|
/**
|
|
* Unattaches events and removes any classes that were added
|
|
*/
|
|
this.destroy = function () {
|
|
|
|
this.initialised = false;
|
|
|
|
for (var i = 0, length = this.elems.length; i < length; i++) {
|
|
|
|
var classList = this.elems[i].classList;
|
|
|
|
classList.remove(this.unPinnedClass);
|
|
classList.remove(this.initialClass);
|
|
classList.remove(this.pinnedClass);
|
|
}
|
|
|
|
var scrollEventName = this.scroller.getScrollEventName ? this.scroller.getScrollEventName() : 'scroll';
|
|
|
|
dom.removeEventListener(this.scroller, scrollEventName, this.debouncer, {
|
|
capture: false,
|
|
passive: true
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Attaches the scroll event
|
|
* @private
|
|
*/
|
|
this.attachEvent = function () {
|
|
if (!this.initialised) {
|
|
this.lastKnownScrollY = this.getScrollY();
|
|
this.initialised = true;
|
|
|
|
var scrollEventName = this.scroller.getScrollEventName ? this.scroller.getScrollEventName() : 'scroll';
|
|
|
|
dom.addEventListener(this.scroller, scrollEventName, this.debouncer, {
|
|
capture: false,
|
|
passive: true
|
|
});
|
|
|
|
this.update();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Unpins the header if it's currently pinned
|
|
*/
|
|
this.clear = function () {
|
|
|
|
if (this.state === 'clear') {
|
|
return;
|
|
}
|
|
|
|
this.state = 'clear';
|
|
|
|
var unpinnedClass = this.unPinnedClass;
|
|
var pinnedClass = this.pinnedClass;
|
|
|
|
for (var i = 0, length = this.elems.length; i < length; i++) {
|
|
var classList = this.elems[i].classList;
|
|
|
|
classList.remove(unpinnedClass);
|
|
//classList.remove(pinnedClass);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Unpins the header if it's currently pinned
|
|
*/
|
|
this.pin = function () {
|
|
|
|
if (this.state === 'pin') {
|
|
return;
|
|
}
|
|
|
|
this.state = 'pin';
|
|
|
|
var unpinnedClass = this.unPinnedClass;
|
|
var pinnedClass = this.pinnedClass;
|
|
|
|
for (var i = 0, length = this.elems.length; i < length; i++) {
|
|
var classList = this.elems[i].classList;
|
|
|
|
classList.remove(unpinnedClass);
|
|
classList.add(pinnedClass);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Unpins the header if it's currently pinned
|
|
*/
|
|
this.unpin = function () {
|
|
|
|
if (this.state === 'unpin') {
|
|
return;
|
|
}
|
|
|
|
this.state = 'unpin';
|
|
|
|
var unpinnedClass = this.unPinnedClass;
|
|
var pinnedClass = this.pinnedClass;
|
|
|
|
for (var i = 0, length = this.elems.length; i < length; i++) {
|
|
var classList = this.elems[i].classList;
|
|
|
|
classList.add(unpinnedClass);
|
|
//classList.remove(pinnedClass);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Gets the Y scroll position
|
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Window.scrollY
|
|
* @return {Number} pixels the page has scrolled along the Y-axis
|
|
*/
|
|
this.getScrollY = function () {
|
|
|
|
var scroller = this.scroller;
|
|
|
|
if (scroller.getScrollPosition) {
|
|
return scroller.getScrollPosition();
|
|
}
|
|
|
|
var pageYOffset = scroller.pageYOffset;
|
|
if (pageYOffset !== undefined) {
|
|
return pageYOffset;
|
|
}
|
|
|
|
var scrollTop = scroller.scrollTop;
|
|
if (scrollTop !== undefined) {
|
|
return scrollTop;
|
|
}
|
|
|
|
return (document.documentElement || document.body).scrollTop;
|
|
};
|
|
|
|
/**
|
|
* determine if it is appropriate to unpin
|
|
* @param {int} currentScrollY the current y scroll position
|
|
* @return {bool} true if should unpin, false otherwise
|
|
*/
|
|
this.shouldUnpin = function (currentScrollY) {
|
|
var scrollingDown = currentScrollY > this.lastKnownScrollY;
|
|
var pastOffset = currentScrollY >= this.offset;
|
|
|
|
return scrollingDown && pastOffset;
|
|
};
|
|
|
|
/**
|
|
* determine if it is appropriate to pin
|
|
* @param {int} currentScrollY the current y scroll position
|
|
* @return {bool} true if should pin, false otherwise
|
|
*/
|
|
this.shouldPin = function (currentScrollY) {
|
|
var scrollingUp = currentScrollY < this.lastKnownScrollY;
|
|
var pastOffset = currentScrollY <= this.offset;
|
|
|
|
return scrollingUp || pastOffset;
|
|
};
|
|
|
|
/**
|
|
* Handles updating the state of the widget
|
|
*/
|
|
this.update = function () {
|
|
|
|
if (this.paused) {
|
|
return;
|
|
}
|
|
|
|
var currentScrollY = this.getScrollY();
|
|
|
|
var lastKnownScrollY = this.lastKnownScrollY;
|
|
|
|
var isTv = layoutManager.tv;
|
|
|
|
if (currentScrollY <= (isTv ? 120 : 10)) {
|
|
this.clear();
|
|
} else if (this.shouldUnpin(currentScrollY)) {
|
|
this.unpin();
|
|
} else if (this.shouldPin(currentScrollY)) {
|
|
|
|
var toleranceExceeded = Math.abs(currentScrollY - lastKnownScrollY) >= 14;
|
|
|
|
if (currentScrollY && isTv) {
|
|
this.unpin();
|
|
} else if (toleranceExceeded) {
|
|
this.clear();
|
|
}
|
|
} else if (isTv) {
|
|
//this.clear();
|
|
}
|
|
|
|
this.lastKnownScrollY = currentScrollY;
|
|
};
|
|
|
|
if (browser.supportsCssAnimation()) {
|
|
for (var i = 0, length = this.elems.length; i < length; i++) {
|
|
this.elems[i].classList.add(this.initialClass);
|
|
this.elems[i].addEventListener('clearheadroom', onHeadroomClearedExternally.bind(this));
|
|
}
|
|
|
|
this.attachEvent();
|
|
}
|
|
}
|
|
|
|
function onScroll() {
|
|
|
|
if (this.paused) {
|
|
return;
|
|
}
|
|
|
|
requestAnimationFrame(this.rafCallback || (this.rafCallback = this.update.bind(this)));
|
|
}
|
|
|
|
/**
|
|
* Default options
|
|
* @type {Object}
|
|
*/
|
|
Headroom.options = {
|
|
offset: 0,
|
|
scroller: window,
|
|
initialClass: 'headroom',
|
|
unPinnedClass: 'headroom--unpinned',
|
|
pinnedClass: 'headroom--pinned'
|
|
};
|
|
|
|
return Headroom;
|
|
});
|