@codingheads/sticky-header
Version:
A library that allows you to create sticky headers. It uses `position: sticky` and IntersectionObserver
245 lines (179 loc) • 8.91 kB
JavaScript
function _classPrivateFieldGet(receiver, privateMap) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get"); return _classApplyDescriptorGet(receiver, descriptor); }
function _classApplyDescriptorGet(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; }
function _classPrivateMethodGet(receiver, privateSet, fn) { if (!privateSet.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return fn; }
function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "set"); _classApplyDescriptorSet(receiver, descriptor, value); return value; }
function _classExtractFieldDescriptor(receiver, privateMap, action) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to " + action + " private field on non-instance"); } return privateMap.get(receiver); }
function _classApplyDescriptorSet(receiver, descriptor, value) { if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } }
var _pinnedClass = /*#__PURE__*/new WeakMap();
var _unpinnedClass = /*#__PURE__*/new WeakMap();
var _mainClass = /*#__PURE__*/new WeakMap();
var _offset = /*#__PURE__*/new WeakMap();
var _positionStickyWorkaround = /*#__PURE__*/new WeakMap();
var _noNativeSupport = /*#__PURE__*/new WeakMap();
var _element = /*#__PURE__*/new WeakMap();
var _observer = /*#__PURE__*/new WeakMap();
var _addBodyClasses = /*#__PURE__*/new WeakMap();
var _init = /*#__PURE__*/new WeakSet();
var _setSticky = /*#__PURE__*/new WeakMap();
/**!
* stickyHeader - JS plugin to create sticky header
* created by Bogdan Barbu
* requires IntersectionObserver
*
* Author: Bogdan Barbu
* Team: Codingheads (codingheads.com)
*
* @format
*/
export default class StickyHeader {
constructor(_element2, {
pinnedClass = 'sticky-pinned',
unpinnedClass = 'sticky-unpinned',
mainClass = 'sticky',
offset = 0,
positionStickyWorkaround = true,
addBodyClasses = true
} = {}) {
_init.add(this);
_pinnedClass.set(this, {
writable: true,
value: 'sticky-pinned'
});
_unpinnedClass.set(this, {
writable: true,
value: 'sticky-unpinned'
});
_mainClass.set(this, {
writable: true,
value: 'sticky'
});
_offset.set(this, {
writable: true,
value: 0
});
_positionStickyWorkaround.set(this, {
writable: true,
value: true
});
_noNativeSupport.set(this, {
writable: true,
value: false
});
_element.set(this, {
writable: true,
value: null
});
_observer.set(this, {
writable: true,
value: null
});
_addBodyClasses.set(this, {
writable: true,
value: true
});
_setSticky.set(this, {
writable: true,
value: entries => {
entries.forEach(entry => {
if (!entry.isIntersecting) {
window.requestAnimationFrame(() => {
[_classPrivateFieldGet(this, _element), ...(_classPrivateFieldGet(this, _addBodyClasses) ? [document.body] : [])].forEach(element => {
element.classList.remove(_classPrivateFieldGet(this, _unpinnedClass));
element.classList.add(_classPrivateFieldGet(this, _pinnedClass));
});
if (_classPrivateFieldGet(this, _noNativeSupport) && _classPrivateFieldGet(this, _addBodyClasses)) {
document.body.style.paddingTop = _classPrivateFieldGet(this, _element).clientHeight + 'px';
} // dispatch an event to tell that we're pinned
_classPrivateFieldGet(this, _element).dispatchEvent(new CustomEvent('stickyIsPinned', {
bubbles: true
}));
});
} else {
window.requestAnimationFrame(() => {
[_classPrivateFieldGet(this, _element), ...(_classPrivateFieldGet(this, _addBodyClasses) ? [document.body] : [])].forEach(element => {
element.classList.add(_classPrivateFieldGet(this, _unpinnedClass));
element.classList.remove(_classPrivateFieldGet(this, _pinnedClass));
});
if (_classPrivateFieldGet(this, _noNativeSupport) && _classPrivateFieldGet(this, _addBodyClasses)) {
document.body.style.paddingTop = '0';
} // dispatch an event to tell that we're unpinned
_classPrivateFieldGet(this, _element).dispatchEvent(new CustomEvent('stickyIsUnpinned', {
bubbles: true
}));
});
}
});
}
});
_classPrivateFieldSet(this, _element, _element2);
_classPrivateFieldSet(this, _pinnedClass, pinnedClass);
_classPrivateFieldSet(this, _unpinnedClass, unpinnedClass);
_classPrivateFieldSet(this, _mainClass, mainClass);
_classPrivateFieldSet(this, _offset, offset);
_classPrivateFieldSet(this, _positionStickyWorkaround, positionStickyWorkaround);
_classPrivateFieldSet(this, _addBodyClasses, addBodyClasses); // does this have native support (Modernizr test)
_classPrivateFieldSet(this, _noNativeSupport, document.documentElement.classList.contains('no-csspositionsticky')); // initialize
_classPrivateMethodGet(this, _init, _init2).call(this);
}
}
function _init2() {
const parent = _classPrivateFieldGet(this, _element).parentElement;
const intersectionItem = document.createElement('div');
let containerPosition = window.getComputedStyle(parent).getPropertyValue('position');
_classPrivateFieldGet(this, _element).classList.add(_classPrivateFieldGet(this, _mainClass)); // add position: relative if the class doesn't add it
if (!containerPosition || containerPosition == 'static') {
parent.style.position = 'relative';
} // use the workaround if position is sticky or there's no offset set up
const stickyPosition = window.getComputedStyle(_classPrivateFieldGet(this, _element), null).getPropertyValue('position');
if (!_classPrivateFieldGet(this, _offset) || stickyPosition == 'sticky') {
_classPrivateFieldSet(this, _positionStickyWorkaround, true);
}
let height = _classPrivateFieldGet(this, _element).clientHeight;
let toObserve = intersectionItem; // add the intersetion observer item
intersectionItem.classList.add('sticky-observer');
intersectionItem.style.pointerEvents = 'none';
intersectionItem.style.visibility = 'hidden';
if (!_classPrivateFieldGet(this, _positionStickyWorkaround)) {
// add the item to the top of the page
intersectionItem.style.position = 'absolute';
intersectionItem.style.top = '0';
intersectionItem.style.left = '0';
intersectionItem.style.right = '0';
intersectionItem.style.height = _classPrivateFieldGet(this, _offset) + 'px';
parent.appendChild(intersectionItem);
} else {
// as a workaround for position: sticky issues, use an element right under the header
intersectionItem.style.position = 'relative';
intersectionItem.style.height = '1px';
if (_classPrivateFieldGet(this, _offset)) {
// if we are using an offset, adjust the "intersection" object position
intersectionItem.style.height = '0';
const intersectionItemOffset = document.createElement('div');
intersectionItemOffset.classList.add('sticky-observer-offset');
intersectionItemOffset.style.position = 'absolute';
intersectionItemOffset.style.height = '1px';
intersectionItemOffset.style.left = '0';
intersectionItemOffset.style.right = '0';
intersectionItemOffset.style.top = _classPrivateFieldGet(this, _offset) - height + 'px';
intersectionItem.appendChild(intersectionItemOffset);
toObserve = intersectionItemOffset; // update the offset when the layout changes
const updateOffset = newHeight => {
if (newHeight) height = newHeight;
intersectionItemOffset.style.top = _classPrivateFieldGet(this, _offset) - height + 'px';
};
['resize', 'orientationchange'].forEach(type => window.addEventListener(type, () => {
updateOffset(_classPrivateFieldGet(this, _element).clientHeight);
}));
}
parent.insertBefore(intersectionItem, _classPrivateFieldGet(this, _element).nextElementSibling);
} // create the observer
_classPrivateFieldSet(this, _observer, new IntersectionObserver(_classPrivateFieldGet(this, _setSticky)));
_classPrivateFieldGet(this, _observer).observe(toObserve);
}
window.StickyHeader = StickyHeader; // register jQuery plugin if jQuery is available
if ('jQuery' in window) {
window.jQuery.fn.stickyHeader = function (options) {
this.each((_i, element) => new StickyHeader(element, options));
return this;
};
}