mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
add passive event listener to headroom
This commit is contained in:
parent
85731c7453
commit
da25a9cc2a
1 changed files with 401 additions and 367 deletions
|
@ -4,16 +4,16 @@
|
||||||
* License: MIT
|
* License: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function(window, document) {
|
(function (window, document) {
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/* exported features */
|
/* exported features */
|
||||||
|
|
||||||
var features = {
|
var features = {
|
||||||
bind : !!(function(){}.bind),
|
bind: !!(function () { }.bind),
|
||||||
classList : 'classList' in document.documentElement,
|
classList: 'classList' in document.documentElement,
|
||||||
rAF : !!(window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame)
|
rAF: !!(window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame)
|
||||||
};
|
};
|
||||||
window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
|
window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
|
||||||
|
|
||||||
|
@ -22,18 +22,18 @@
|
||||||
* @see http://www.html5rocks.com/en/tutorials/speed/animations/
|
* @see http://www.html5rocks.com/en/tutorials/speed/animations/
|
||||||
* @param {Function} callback The callback to handle whichever event
|
* @param {Function} callback The callback to handle whichever event
|
||||||
*/
|
*/
|
||||||
function Debouncer (callback) {
|
function Debouncer(callback) {
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.ticking = false;
|
this.ticking = false;
|
||||||
}
|
}
|
||||||
Debouncer.prototype = {
|
Debouncer.prototype = {
|
||||||
constructor : Debouncer,
|
constructor: Debouncer,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* dispatches the event to the supplied callback
|
* dispatches the event to the supplied callback
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
update : function() {
|
update: function () {
|
||||||
this.callback && this.callback();
|
this.callback && this.callback();
|
||||||
this.ticking = false;
|
this.ticking = false;
|
||||||
},
|
},
|
||||||
|
@ -42,8 +42,8 @@
|
||||||
* ensures events don't get stacked
|
* ensures events don't get stacked
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
requestTick : function() {
|
requestTick: function () {
|
||||||
if(!this.ticking) {
|
if (!this.ticking) {
|
||||||
requestAnimationFrame(this.rafCallback || (this.rafCallback = this.update.bind(this)));
|
requestAnimationFrame(this.rafCallback || (this.rafCallback = this.update.bind(this)));
|
||||||
this.ticking = true;
|
this.ticking = true;
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
/**
|
/**
|
||||||
* Attach this as the event listeners
|
* Attach this as the event listeners
|
||||||
*/
|
*/
|
||||||
handleEvent : function() {
|
handleEvent: function () {
|
||||||
this.requestTick();
|
this.requestTick();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -68,8 +68,8 @@
|
||||||
/**
|
/**
|
||||||
* Helper function for extending objects
|
* Helper function for extending objects
|
||||||
*/
|
*/
|
||||||
function extend (object /*, objectN ... */) {
|
function extend(object /*, objectN ... */) {
|
||||||
if(arguments.length <= 0) {
|
if (arguments.length <= 0) {
|
||||||
throw new Error('Missing arguments in extend function');
|
throw new Error('Missing arguments in extend function');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@
|
||||||
|
|
||||||
for (key in replacement) {
|
for (key in replacement) {
|
||||||
// Recurse into object except if the object is a DOM element
|
// Recurse into object except if the object is a DOM element
|
||||||
if(typeof result[key] === 'object' && ! isDOMElement(result[key])) {
|
if (typeof result[key] === 'object' && !isDOMElement(result[key])) {
|
||||||
result[key] = extend(result[key], replacement[key]);
|
result[key] = extend(result[key], replacement[key]);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -97,8 +97,34 @@
|
||||||
/**
|
/**
|
||||||
* Helper function for normalizing tolerance option to object format
|
* Helper function for normalizing tolerance option to object format
|
||||||
*/
|
*/
|
||||||
function normalizeTolerance (t) {
|
function normalizeTolerance(t) {
|
||||||
return t === Object(t) ? t : { down : t, up : t };
|
return t === Object(t) ? t : { down: t, up: t };
|
||||||
|
}
|
||||||
|
|
||||||
|
var supportsCaptureOption = false;
|
||||||
|
try {
|
||||||
|
var opts = Object.defineProperty({}, 'capture', {
|
||||||
|
get: function () {
|
||||||
|
supportsCaptureOption = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener("test", null, opts);
|
||||||
|
} catch (e) { }
|
||||||
|
|
||||||
|
function addEventListenerWithOptions(target, type, handler, options) {
|
||||||
|
var optionsOrCapture = options;
|
||||||
|
if (!supportsCaptureOption) {
|
||||||
|
optionsOrCapture = options.capture;
|
||||||
|
}
|
||||||
|
target.addEventListener(type, handler, optionsOrCapture);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeEventListenerWithOptions(target, type, handler, options) {
|
||||||
|
var optionsOrCapture = options;
|
||||||
|
if (!supportsCaptureOption) {
|
||||||
|
optionsOrCapture = options.capture;
|
||||||
|
}
|
||||||
|
target.removeEventListener(type, handler, optionsOrCapture);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -109,7 +135,7 @@
|
||||||
* @param {DOMElement} elem the header element
|
* @param {DOMElement} elem the header element
|
||||||
* @param {Object} options options for the widget
|
* @param {Object} options options for the widget
|
||||||
*/
|
*/
|
||||||
function Headroom (elem, options) {
|
function Headroom(elem, options) {
|
||||||
options = extend(options, Headroom.options);
|
options = extend(options, Headroom.options);
|
||||||
|
|
||||||
this.lastKnownScrollY = 0;
|
this.lastKnownScrollY = 0;
|
||||||
|
@ -126,21 +152,19 @@
|
||||||
this.onNotTop = options.onNotTop;
|
this.onNotTop = options.onNotTop;
|
||||||
}
|
}
|
||||||
Headroom.prototype = {
|
Headroom.prototype = {
|
||||||
constructor : Headroom,
|
constructor: Headroom,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialises the widget
|
* Initialises the widget
|
||||||
*/
|
*/
|
||||||
init : function() {
|
init: function () {
|
||||||
if(!Headroom.cutsTheMustard) {
|
if (!Headroom.cutsTheMustard) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.elem.classList.add(this.classes.initial);
|
this.elem.classList.add(this.classes.initial);
|
||||||
|
|
||||||
// defer event registration to handle browser
|
this.attachEvent();
|
||||||
// potentially restoring previous scroll position
|
|
||||||
setTimeout(this.attachEvent.bind(this), 100);
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
@ -148,23 +172,29 @@
|
||||||
/**
|
/**
|
||||||
* Unattaches events and removes any classes that were added
|
* Unattaches events and removes any classes that were added
|
||||||
*/
|
*/
|
||||||
destroy : function() {
|
destroy: function () {
|
||||||
var classes = this.classes;
|
var classes = this.classes;
|
||||||
|
|
||||||
this.initialised = false;
|
this.initialised = false;
|
||||||
this.elem.classList.remove(classes.unpinned, classes.pinned, classes.top, classes.initial);
|
this.elem.classList.remove(classes.unpinned, classes.pinned, classes.top, classes.initial);
|
||||||
this.scroller.removeEventListener('scroll', this.debouncer, false);
|
removeEventListenerWithOptions(this.scroller, 'scroll', this.debouncer, {
|
||||||
|
capture: false,
|
||||||
|
passive: true
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attaches the scroll event
|
* Attaches the scroll event
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
attachEvent : function() {
|
attachEvent: function () {
|
||||||
if(!this.initialised){
|
if (!this.initialised) {
|
||||||
this.lastKnownScrollY = this.getScrollY();
|
this.lastKnownScrollY = this.getScrollY();
|
||||||
this.initialised = true;
|
this.initialised = true;
|
||||||
this.scroller.addEventListener('scroll', this.debouncer, false);
|
addEventListenerWithOptions(this.scroller, 'scroll', this.debouncer, {
|
||||||
|
capture: false,
|
||||||
|
passive: true
|
||||||
|
});
|
||||||
|
|
||||||
this.debouncer.handleEvent();
|
this.debouncer.handleEvent();
|
||||||
}
|
}
|
||||||
|
@ -173,11 +203,11 @@
|
||||||
/**
|
/**
|
||||||
* Unpins the header if it's currently pinned
|
* Unpins the header if it's currently pinned
|
||||||
*/
|
*/
|
||||||
unpin : function() {
|
unpin: function () {
|
||||||
var classList = this.elem.classList,
|
var classList = this.elem.classList,
|
||||||
classes = this.classes;
|
classes = this.classes;
|
||||||
|
|
||||||
if(classList.contains(classes.pinned) || !classList.contains(classes.unpinned)) {
|
if (classList.contains(classes.pinned) || !classList.contains(classes.unpinned)) {
|
||||||
classList.add(classes.unpinned);
|
classList.add(classes.unpinned);
|
||||||
classList.remove(classes.pinned);
|
classList.remove(classes.pinned);
|
||||||
this.onUnpin && this.onUnpin.call(this);
|
this.onUnpin && this.onUnpin.call(this);
|
||||||
|
@ -187,11 +217,11 @@
|
||||||
/**
|
/**
|
||||||
* Pins the header if it's currently unpinned
|
* Pins the header if it's currently unpinned
|
||||||
*/
|
*/
|
||||||
pin : function() {
|
pin: function () {
|
||||||
var classList = this.elem.classList,
|
var classList = this.elem.classList,
|
||||||
classes = this.classes;
|
classes = this.classes;
|
||||||
|
|
||||||
if(classList.contains(classes.unpinned)) {
|
if (classList.contains(classes.unpinned)) {
|
||||||
classList.remove(classes.unpinned);
|
classList.remove(classes.unpinned);
|
||||||
classList.add(classes.pinned);
|
classList.add(classes.pinned);
|
||||||
this.onPin && this.onPin.call(this);
|
this.onPin && this.onPin.call(this);
|
||||||
|
@ -201,11 +231,11 @@
|
||||||
/**
|
/**
|
||||||
* Handles the top states
|
* Handles the top states
|
||||||
*/
|
*/
|
||||||
top : function() {
|
top: function () {
|
||||||
var classList = this.elem.classList,
|
var classList = this.elem.classList,
|
||||||
classes = this.classes;
|
classes = this.classes;
|
||||||
|
|
||||||
if(!classList.contains(classes.top)) {
|
if (!classList.contains(classes.top)) {
|
||||||
classList.add(classes.top);
|
classList.add(classes.top);
|
||||||
classList.remove(classes.notTop);
|
classList.remove(classes.notTop);
|
||||||
this.onTop && this.onTop.call(this);
|
this.onTop && this.onTop.call(this);
|
||||||
|
@ -215,11 +245,11 @@
|
||||||
/**
|
/**
|
||||||
* Handles the not top state
|
* Handles the not top state
|
||||||
*/
|
*/
|
||||||
notTop : function() {
|
notTop: function () {
|
||||||
var classList = this.elem.classList,
|
var classList = this.elem.classList,
|
||||||
classes = this.classes;
|
classes = this.classes;
|
||||||
|
|
||||||
if(!classList.contains(classes.notTop)) {
|
if (!classList.contains(classes.notTop)) {
|
||||||
classList.add(classes.notTop);
|
classList.add(classes.notTop);
|
||||||
classList.remove(classes.top);
|
classList.remove(classes.top);
|
||||||
this.onNotTop && this.onNotTop.call(this);
|
this.onNotTop && this.onNotTop.call(this);
|
||||||
|
@ -231,7 +261,7 @@
|
||||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/Window.scrollY
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Window.scrollY
|
||||||
* @return {Number} pixels the page has scrolled along the Y-axis
|
* @return {Number} pixels the page has scrolled along the Y-axis
|
||||||
*/
|
*/
|
||||||
getScrollY : function() {
|
getScrollY: function () {
|
||||||
return (this.scroller.pageYOffset !== undefined)
|
return (this.scroller.pageYOffset !== undefined)
|
||||||
? this.scroller.pageYOffset
|
? this.scroller.pageYOffset
|
||||||
: (this.scroller.scrollTop !== undefined)
|
: (this.scroller.scrollTop !== undefined)
|
||||||
|
@ -244,7 +274,7 @@
|
||||||
* @see http://andylangton.co.uk/blog/development/get-viewport-size-width-and-height-javascript
|
* @see http://andylangton.co.uk/blog/development/get-viewport-size-width-and-height-javascript
|
||||||
* @return {int} the height of the viewport in pixels
|
* @return {int} the height of the viewport in pixels
|
||||||
*/
|
*/
|
||||||
getViewportHeight : function () {
|
getViewportHeight: function () {
|
||||||
return window.innerHeight
|
return window.innerHeight
|
||||||
|| document.documentElement.clientHeight
|
|| document.documentElement.clientHeight
|
||||||
|| document.body.clientHeight;
|
|| document.body.clientHeight;
|
||||||
|
@ -255,7 +285,7 @@
|
||||||
* @see http://james.padolsey.com/javascript/get-document-height-cross-browser/
|
* @see http://james.padolsey.com/javascript/get-document-height-cross-browser/
|
||||||
* @return {int} the height of the document in pixels
|
* @return {int} the height of the document in pixels
|
||||||
*/
|
*/
|
||||||
getDocumentHeight : function () {
|
getDocumentHeight: function () {
|
||||||
var body = document.body,
|
var body = document.body,
|
||||||
documentElement = document.documentElement;
|
documentElement = document.documentElement;
|
||||||
|
|
||||||
|
@ -271,7 +301,7 @@
|
||||||
* @param {Object} elm the element to calculate the height of which
|
* @param {Object} elm the element to calculate the height of which
|
||||||
* @return {int} the height of the element in pixels
|
* @return {int} the height of the element in pixels
|
||||||
*/
|
*/
|
||||||
getElementHeight : function (elm) {
|
getElementHeight: function (elm) {
|
||||||
return Math.max(
|
return Math.max(
|
||||||
elm.scrollHeight,
|
elm.scrollHeight,
|
||||||
elm.offsetHeight,
|
elm.offsetHeight,
|
||||||
|
@ -283,7 +313,7 @@
|
||||||
* Gets the height of the scroller element
|
* Gets the height of the scroller element
|
||||||
* @return {int} the height of the scroller element in pixels
|
* @return {int} the height of the scroller element in pixels
|
||||||
*/
|
*/
|
||||||
getScrollerHeight : function () {
|
getScrollerHeight: function () {
|
||||||
return (this.scroller === window || this.scroller === document.body)
|
return (this.scroller === window || this.scroller === document.body)
|
||||||
? this.getDocumentHeight()
|
? this.getDocumentHeight()
|
||||||
: this.getElementHeight(this.scroller);
|
: this.getElementHeight(this.scroller);
|
||||||
|
@ -294,11 +324,15 @@
|
||||||
* @param {int} currentScrollY the current y scroll position
|
* @param {int} currentScrollY the current y scroll position
|
||||||
* @return {bool} true if out of bounds, false otherwise
|
* @return {bool} true if out of bounds, false otherwise
|
||||||
*/
|
*/
|
||||||
isOutOfBounds : function (currentScrollY) {
|
isOutOfBounds: function (currentScrollY) {
|
||||||
var pastTop = currentScrollY < 0,
|
var pastTop = currentScrollY < 0;
|
||||||
pastBottom = currentScrollY + this.getViewportHeight() > this.getScrollerHeight();
|
|
||||||
|
|
||||||
return pastTop || pastBottom;
|
if (pastTop) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pastBottom = currentScrollY + this.getViewportHeight() > this.getScrollerHeight();
|
||||||
|
return pastBottom;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -306,8 +340,8 @@
|
||||||
* @param {int} currentScrollY the current scroll y position
|
* @param {int} currentScrollY the current scroll y position
|
||||||
* @return {bool} true if tolerance exceeded, false otherwise
|
* @return {bool} true if tolerance exceeded, false otherwise
|
||||||
*/
|
*/
|
||||||
toleranceExceeded : function (currentScrollY, direction) {
|
toleranceExceeded: function (currentScrollY, direction) {
|
||||||
return Math.abs(currentScrollY-this.lastKnownScrollY) >= this.tolerance[direction];
|
return Math.abs(currentScrollY - this.lastKnownScrollY) >= this.tolerance[direction];
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -316,7 +350,7 @@
|
||||||
* @param {bool} toleranceExceeded has the tolerance been exceeded?
|
* @param {bool} toleranceExceeded has the tolerance been exceeded?
|
||||||
* @return {bool} true if should unpin, false otherwise
|
* @return {bool} true if should unpin, false otherwise
|
||||||
*/
|
*/
|
||||||
shouldUnpin : function (currentScrollY, toleranceExceeded) {
|
shouldUnpin: function (currentScrollY, toleranceExceeded) {
|
||||||
var scrollingDown = currentScrollY > this.lastKnownScrollY,
|
var scrollingDown = currentScrollY > this.lastKnownScrollY,
|
||||||
pastOffset = currentScrollY >= this.offset;
|
pastOffset = currentScrollY >= this.offset;
|
||||||
|
|
||||||
|
@ -329,7 +363,7 @@
|
||||||
* @param {bool} toleranceExceeded has the tolerance been exceeded?
|
* @param {bool} toleranceExceeded has the tolerance been exceeded?
|
||||||
* @return {bool} true if should pin, false otherwise
|
* @return {bool} true if should pin, false otherwise
|
||||||
*/
|
*/
|
||||||
shouldPin : function (currentScrollY, toleranceExceeded) {
|
shouldPin: function (currentScrollY, toleranceExceeded) {
|
||||||
var scrollingUp = currentScrollY < this.lastKnownScrollY,
|
var scrollingUp = currentScrollY < this.lastKnownScrollY,
|
||||||
pastOffset = currentScrollY <= this.offset;
|
pastOffset = currentScrollY <= this.offset;
|
||||||
|
|
||||||
|
@ -339,25 +373,25 @@
|
||||||
/**
|
/**
|
||||||
* Handles updating the state of the widget
|
* Handles updating the state of the widget
|
||||||
*/
|
*/
|
||||||
update : function() {
|
update: function () {
|
||||||
var currentScrollY = this.getScrollY(),
|
var currentScrollY = this.getScrollY(),
|
||||||
scrollDirection = currentScrollY > this.lastKnownScrollY ? 'down' : 'up',
|
scrollDirection = currentScrollY > this.lastKnownScrollY ? 'down' : 'up',
|
||||||
toleranceExceeded = this.toleranceExceeded(currentScrollY, scrollDirection);
|
toleranceExceeded = this.toleranceExceeded(currentScrollY, scrollDirection);
|
||||||
|
|
||||||
if(this.isOutOfBounds(currentScrollY)) { // Ignore bouncy scrolling in OSX
|
if (this.isOutOfBounds(currentScrollY)) { // Ignore bouncy scrolling in OSX
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentScrollY <= this.offset ) {
|
if (currentScrollY <= this.offset) {
|
||||||
this.top();
|
this.top();
|
||||||
} else {
|
} else {
|
||||||
this.notTop();
|
this.notTop();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.shouldUnpin(currentScrollY, toleranceExceeded)) {
|
if (this.shouldUnpin(currentScrollY, toleranceExceeded)) {
|
||||||
this.unpin();
|
this.unpin();
|
||||||
}
|
}
|
||||||
else if(this.shouldPin(currentScrollY, toleranceExceeded)) {
|
else if (this.shouldPin(currentScrollY, toleranceExceeded)) {
|
||||||
this.pin();
|
this.pin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -369,18 +403,18 @@
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
Headroom.options = {
|
Headroom.options = {
|
||||||
tolerance : {
|
tolerance: {
|
||||||
up : 0,
|
up: 0,
|
||||||
down : 0
|
down: 0
|
||||||
},
|
},
|
||||||
offset : 0,
|
offset: 0,
|
||||||
scroller: window,
|
scroller: window,
|
||||||
classes : {
|
classes: {
|
||||||
pinned : 'headroom--pinned',
|
pinned: 'headroom--pinned',
|
||||||
unpinned : 'headroom--unpinned',
|
unpinned: 'headroom--unpinned',
|
||||||
top : 'headroom--top',
|
top: 'headroom--top',
|
||||||
notTop : 'headroom--not-top',
|
notTop: 'headroom--not-top',
|
||||||
initial : 'headroom'
|
initial: 'headroom'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Headroom.cutsTheMustard = typeof features !== 'undefined' && features.rAF && features.bind && features.classList;
|
Headroom.cutsTheMustard = typeof features !== 'undefined' && features.rAF && features.bind && features.classList;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue