139 lines
4.6 KiB
JavaScript
139 lines
4.6 KiB
JavaScript
import options from './_options';
|
|
import version from '../_version';
|
|
/**
|
|
* Class for a sticky navigational header.
|
|
*/
|
|
export default class Mhead {
|
|
/**
|
|
* Create a sticky header.
|
|
* @param {HTMLElement|string} header The header node.
|
|
* @param {object} [options=Mhead.options] Options for the header.
|
|
*/
|
|
constructor(header, options = Mhead.options) {
|
|
// Get header node from string or element.
|
|
this.header =
|
|
typeof header == 'string' ? document.querySelector(header) : header;
|
|
// Stop if there is no header element found.
|
|
if (!header) {
|
|
return;
|
|
}
|
|
// Extend options from defaults.
|
|
this.opts = Object.assign(options, Mhead.options);
|
|
this.initHooks();
|
|
this.initScroll();
|
|
}
|
|
/**
|
|
* Initiate the scroll functionality.
|
|
*/
|
|
initScroll() {
|
|
if (!this.opts.scroll || this.opts.scroll.unpin === false) {
|
|
return;
|
|
}
|
|
this.header.classList.add('mh-sticky');
|
|
/** Minimum scroll position to unpin / hide the header. */
|
|
var _min = this.header.offsetHeight * 2;
|
|
this.opts.scroll.unpin = Math.max(_min, this.opts.scroll.unpin || 0);
|
|
this.opts.scroll.pin = Math.max(_min, this.opts.scroll.pin || 0);
|
|
this.state = null;
|
|
/** Previous scroll position. */
|
|
var lastYpos = 0;
|
|
const onscroll = (evnt = {}) => {
|
|
/** Current scroll position. */
|
|
var pos = document.documentElement.scrollTop || document.body.scrollTop;
|
|
/** Difference between current scroll position and previous scroll position. */
|
|
var dif = lastYpos - pos;
|
|
/** Direction of the scroll. */
|
|
var dir = dif < 0 ? 'down' : 'up';
|
|
dif = Math.abs(dif);
|
|
lastYpos = pos;
|
|
// If not pinned / scrolled out the viewport.
|
|
if (this.state == Mhead.UNPINNED) {
|
|
// If scrolling up
|
|
if (dir == 'up') {
|
|
// If scrolling fast enough or past minimum
|
|
if (pos < this.opts.scroll.pin ||
|
|
dif > this.opts.scroll.tolerance) {
|
|
this.pin();
|
|
}
|
|
}
|
|
}
|
|
// If pinned / not scrolled out the viewport.
|
|
else if (this.state == Mhead.PINNED) {
|
|
// If scrolling down.
|
|
if (dir == 'down') {
|
|
// If scrolling fast enough and past minimum.
|
|
if (pos > this.opts.scroll.unpin &&
|
|
dif > this.opts.scroll.tolerance) {
|
|
this.unpin();
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
this.pin();
|
|
}
|
|
};
|
|
window.addEventListener('scroll', onscroll, {
|
|
passive: true
|
|
});
|
|
onscroll();
|
|
}
|
|
/**
|
|
* Pin the header to the top of the viewport.
|
|
*/
|
|
pin() {
|
|
this.header.classList.add('mh-pinned');
|
|
this.header.classList.remove('mh-unpinned');
|
|
this.state = Mhead.PINNED;
|
|
this.trigger('pinned');
|
|
}
|
|
/**
|
|
* Release the header from the top of the viewport.
|
|
*/
|
|
unpin() {
|
|
this.header.classList.remove('mh-pinned');
|
|
this.header.classList.add('mh-unpinned');
|
|
this.state = Mhead.UNPINNED;
|
|
this.trigger('unpinned');
|
|
}
|
|
/**
|
|
* Bind the hooks specified in the options (publisher).
|
|
*/
|
|
initHooks() {
|
|
this.hooks = {};
|
|
for (let hook in this.opts.hooks) {
|
|
this.bind(hook, this.opts.hooks[hook]);
|
|
}
|
|
}
|
|
/**
|
|
* Bind functions to a hook (subscriber).
|
|
* @param {string} hook The hook.
|
|
* @param {function} func The function.
|
|
*/
|
|
bind(hook, func) {
|
|
// Create an array for the hook if it does not yet excist.
|
|
this.hooks[hook] = this.hooks[hook] || [];
|
|
// Push the function to the array.
|
|
this.hooks[hook].push(func);
|
|
}
|
|
/**
|
|
* Invoke the functions bound to a hook (publisher).
|
|
* @param {string} hook The hook.
|
|
* @param {array} [args] Arguments for the function.
|
|
*/
|
|
trigger(hook, args) {
|
|
if (this.hooks[hook]) {
|
|
for (var h = 0, l = this.hooks[hook].length; h < l; h++) {
|
|
this.hooks[hook][h].apply(this, args);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/** Plugin version. */
|
|
Mhead.version = version;
|
|
/** Default options for headers. */
|
|
Mhead.options = options;
|
|
/** State for a "pinned" header. */
|
|
Mhead.PINNED = 'pinned';
|
|
/** State for a "unpinned" header. */
|
|
Mhead.UNPINNED = 'unpinned';
|