| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480 |
- /*!
- * Stickyfill -- `position: sticky` polyfill
- * v. 1.1.4 | https://github.com/wilddeer/stickyfill
- * Copyright Oleg Korsunsky | http://wd.dizaina.net/
- *
- * MIT License
- */
- (function(doc, win) {
- var watchArray = [],
- scroll,
- initialized = false,
- html = doc.documentElement,
- noop = function() {},
- checkTimer,
- //visibility API strings
- hiddenPropertyName = 'hidden',
- visibilityChangeEventName = 'visibilitychange';
- //fallback to prefixed names in old webkit browsers
- if (doc.webkitHidden !== undefined) {
- hiddenPropertyName = 'webkitHidden';
- visibilityChangeEventName = 'webkitvisibilitychange';
- }
- //test getComputedStyle
- if (!win.getComputedStyle) {
- seppuku();
- }
- //test for native support
- var prefixes = ['', '-webkit-', '-moz-', '-ms-'],
- block = document.createElement('div');
- for (var i = prefixes.length - 1; i >= 0; i--) {
- try {
- block.style.position = prefixes[i] + 'sticky';
- }
- catch(e) {}
- if (block.style.position != '') {
- seppuku();
- }
- }
- updateScrollPos();
- //commit seppuku!
- function seppuku() {
- init = add = rebuild = pause = stop = kill = noop;
- }
- function mergeObjects(targetObj, sourceObject) {
- for (var key in sourceObject) {
- if (sourceObject.hasOwnProperty(key)) {
- targetObj[key] = sourceObject[key];
- }
- }
- }
- function parseNumeric(val) {
- return parseFloat(val) || 0;
- }
- function updateScrollPos() {
- scroll = {
- top: win.pageYOffset,
- left: win.pageXOffset
- };
- }
- function onScroll() {
- if (win.pageXOffset != scroll.left) {
- updateScrollPos();
- rebuild();
- return;
- }
-
- if (win.pageYOffset != scroll.top) {
- updateScrollPos();
- recalcAllPos();
- }
- }
- //fixes flickering
- function onWheel(event) {
- setTimeout(function() {
- if (win.pageYOffset != scroll.top) {
- scroll.top = win.pageYOffset;
- recalcAllPos();
- }
- }, 0);
- }
- function recalcAllPos() {
- for (var i = watchArray.length - 1; i >= 0; i--) {
- recalcElementPos(watchArray[i]);
- }
- }
- function recalcElementPos(el) {
- if (!el.inited) return;
- var currentMode = (scroll.top <= el.limit.start? 0: scroll.top >= el.limit.end? 2: 1);
- if (el.mode != currentMode) {
- switchElementMode(el, currentMode);
- }
- }
- //checks whether stickies start or stop positions have changed
- function fastCheck() {
- for (var i = watchArray.length - 1; i >= 0; i--) {
- if (!watchArray[i].inited) continue;
- var deltaTop = Math.abs(getDocOffsetTop(watchArray[i].clone) - watchArray[i].docOffsetTop),
- deltaHeight = Math.abs(watchArray[i].parent.node.offsetHeight - watchArray[i].parent.height);
- if (deltaTop >= 2 || deltaHeight >= 2) return false;
- }
- return true;
- }
- function initElement(el) {
- if (isNaN(parseFloat(el.computed.top)) || el.isCell || el.computed.display == 'none') return;
- el.inited = true;
- if (!el.clone) clone(el);
- if (el.parent.computed.position != 'absolute' &&
- el.parent.computed.position != 'relative') el.parent.node.style.position = 'relative';
- recalcElementPos(el);
- el.parent.height = el.parent.node.offsetHeight;
- el.docOffsetTop = getDocOffsetTop(el.clone);
- }
- function deinitElement(el) {
- var deinitParent = true;
- el.clone && killClone(el);
- mergeObjects(el.node.style, el.css);
- //check whether element's parent is used by other stickies
- for (var i = watchArray.length - 1; i >= 0; i--) {
- if (watchArray[i].node !== el.node && watchArray[i].parent.node === el.parent.node) {
- deinitParent = false;
- break;
- }
- };
- if (deinitParent) el.parent.node.style.position = el.parent.css.position;
- el.mode = -1;
- }
- function initAll() {
- for (var i = watchArray.length - 1; i >= 0; i--) {
- initElement(watchArray[i]);
- }
- }
- function deinitAll() {
- for (var i = watchArray.length - 1; i >= 0; i--) {
- deinitElement(watchArray[i]);
- }
- }
- function switchElementMode(el, mode) {
- var nodeStyle = el.node.style;
- switch (mode) {
- case 0:
- nodeStyle.position = 'absolute';
- nodeStyle.left = el.offset.left + 'px';
- nodeStyle.right = el.offset.right + 'px';
- nodeStyle.top = el.offset.top + 'px';
- nodeStyle.bottom = 'auto';
- nodeStyle.width = 'auto';
- nodeStyle.marginLeft = 0;
- nodeStyle.marginRight = 0;
- nodeStyle.marginTop = 0;
- break;
- case 1:
- nodeStyle.position = 'fixed';
- nodeStyle.left = el.box.left + 'px';
- nodeStyle.right = el.box.right + 'px';
- nodeStyle.top = el.css.top;
- nodeStyle.bottom = 'auto';
- nodeStyle.width = 'auto';
- nodeStyle.marginLeft = 0;
- nodeStyle.marginRight = 0;
- nodeStyle.marginTop = 0;
- break;
- case 2:
- nodeStyle.position = 'absolute';
- nodeStyle.left = el.offset.left + 'px';
- nodeStyle.right = el.offset.right + 'px';
- nodeStyle.top = 'auto';
- nodeStyle.bottom = 0;
- nodeStyle.width = 'auto';
- nodeStyle.marginLeft = 0;
- nodeStyle.marginRight = 0;
- break;
- }
- el.mode = mode;
- }
- function clone(el) {
- el.clone = document.createElement('div');
- var refElement = el.node.nextSibling || el.node,
- cloneStyle = el.clone.style;
- cloneStyle.height = el.height + 'px';
- cloneStyle.width = el.width + 'px';
- cloneStyle.marginTop = el.computed.marginTop;
- cloneStyle.marginBottom = el.computed.marginBottom;
- cloneStyle.marginLeft = el.computed.marginLeft;
- cloneStyle.marginRight = el.computed.marginRight;
- cloneStyle.padding = cloneStyle.border = cloneStyle.borderSpacing = 0;
- cloneStyle.fontSize = '1em';
- cloneStyle.position = 'static';
- cloneStyle.cssFloat = el.computed.cssFloat;
- el.node.parentNode.insertBefore(el.clone, refElement);
- }
- function killClone(el) {
- el.clone.parentNode.removeChild(el.clone);
- el.clone = undefined;
- }
- function getElementParams(node) {
- var computedStyle = getComputedStyle(node),
- parentNode = node.parentNode,
- parentComputedStyle = getComputedStyle(parentNode),
- cachedPosition = node.style.position;
- node.style.position = 'relative';
- var computed = {
- top: computedStyle.top,
- marginTop: computedStyle.marginTop,
- marginBottom: computedStyle.marginBottom,
- marginLeft: computedStyle.marginLeft,
- marginRight: computedStyle.marginRight,
- cssFloat: computedStyle.cssFloat,
- display: computedStyle.display
- },
- numeric = {
- top: parseNumeric(computedStyle.top),
- marginBottom: parseNumeric(computedStyle.marginBottom),
- paddingLeft: parseNumeric(computedStyle.paddingLeft),
- paddingRight: parseNumeric(computedStyle.paddingRight),
- borderLeftWidth: parseNumeric(computedStyle.borderLeftWidth),
- borderRightWidth: parseNumeric(computedStyle.borderRightWidth)
- };
- node.style.position = cachedPosition;
- var css = {
- position: node.style.position,
- top: node.style.top,
- bottom: node.style.bottom,
- left: node.style.left,
- right: node.style.right,
- width: node.style.width,
- marginTop: node.style.marginTop,
- marginLeft: node.style.marginLeft,
- marginRight: node.style.marginRight
- },
- nodeOffset = getElementOffset(node),
- parentOffset = getElementOffset(parentNode),
-
- parent = {
- node: parentNode,
- css: {
- position: parentNode.style.position
- },
- computed: {
- position: parentComputedStyle.position
- },
- numeric: {
- borderLeftWidth: parseNumeric(parentComputedStyle.borderLeftWidth),
- borderRightWidth: parseNumeric(parentComputedStyle.borderRightWidth),
- borderTopWidth: parseNumeric(parentComputedStyle.borderTopWidth),
- borderBottomWidth: parseNumeric(parentComputedStyle.borderBottomWidth)
- }
- },
- el = {
- node: node,
- box: {
- left: nodeOffset.win.left,
- right: html.clientWidth - nodeOffset.win.right
- },
- offset: {
- top: nodeOffset.win.top - parentOffset.win.top - parent.numeric.borderTopWidth,
- left: nodeOffset.win.left - parentOffset.win.left - parent.numeric.borderLeftWidth,
- right: -nodeOffset.win.right + parentOffset.win.right - parent.numeric.borderRightWidth
- },
- css: css,
- isCell: computedStyle.display == 'table-cell',
- computed: computed,
- numeric: numeric,
- width: nodeOffset.win.right - nodeOffset.win.left,
- height: nodeOffset.win.bottom - nodeOffset.win.top,
- mode: -1,
- inited: false,
- parent: parent,
- limit: {
- start: nodeOffset.doc.top - numeric.top,
- end: parentOffset.doc.top + parentNode.offsetHeight - parent.numeric.borderBottomWidth -
- node.offsetHeight - numeric.top - numeric.marginBottom
- }
- };
- return el;
- }
- function getDocOffsetTop(node) {
- var docOffsetTop = 0;
- while (node) {
- docOffsetTop += node.offsetTop;
- node = node.offsetParent;
- }
- return docOffsetTop;
- }
- function getElementOffset(node) {
- var box = node.getBoundingClientRect();
- return {
- doc: {
- top: box.top + win.pageYOffset,
- left: box.left + win.pageXOffset
- },
- win: box
- };
- }
- function startFastCheckTimer() {
- checkTimer = setInterval(function() {
- !fastCheck() && rebuild();
- }, 500);
- }
- function stopFastCheckTimer() {
- clearInterval(checkTimer);
- }
- function handlePageVisibilityChange() {
- if (!initialized) return;
- if (document[hiddenPropertyName]) {
- stopFastCheckTimer();
- }
- else {
- startFastCheckTimer();
- }
- }
- function init() {
- if (initialized) return;
- updateScrollPos();
- initAll();
- win.addEventListener('scroll', onScroll);
- win.addEventListener('wheel', onWheel);
- //watch for width changes
- win.addEventListener('resize', rebuild);
- win.addEventListener('orientationchange', rebuild);
- //watch for page visibility
- doc.addEventListener(visibilityChangeEventName, handlePageVisibilityChange);
- startFastCheckTimer();
- initialized = true;
- }
- function rebuild() {
- if (!initialized) return;
- deinitAll();
-
- for (var i = watchArray.length - 1; i >= 0; i--) {
- watchArray[i] = getElementParams(watchArray[i].node);
- }
-
- initAll();
- }
- function pause() {
- win.removeEventListener('scroll', onScroll);
- win.removeEventListener('wheel', onWheel);
- win.removeEventListener('resize', rebuild);
- win.removeEventListener('orientationchange', rebuild);
- doc.removeEventListener(visibilityChangeEventName, handlePageVisibilityChange);
- stopFastCheckTimer();
- initialized = false;
- }
- function stop() {
- pause();
- deinitAll();
- }
- function kill() {
- stop();
- //empty the array without loosing the references,
- //the most performant method according to http://jsperf.com/empty-javascript-array
- while (watchArray.length) {
- watchArray.pop();
- }
- }
- function add(node) {
- //check if Stickyfill is already applied to the node
- for (var i = watchArray.length - 1; i >= 0; i--) {
- if (watchArray[i].node === node) return;
- };
- var el = getElementParams(node);
- watchArray.push(el);
- if (!initialized) {
- init();
- }
- else {
- initElement(el);
- }
- }
- function remove(node) {
- for (var i = watchArray.length - 1; i >= 0; i--) {
- if (watchArray[i].node === node) {
- deinitElement(watchArray[i]);
- watchArray.splice(i, 1);
- }
- };
- }
- //expose Stickyfill
- win.Stickyfill = {
- stickies: watchArray,
- add: add,
- remove: remove,
- init: init,
- rebuild: rebuild,
- pause: pause,
- stop: stop,
- kill: kill
- };
- })(document, window);
- //if jQuery is available -- create a plugin
- if (window.jQuery) {
- (function($) {
- $.fn.Stickyfill = function(options) {
- this.each(function() {
- Stickyfill.add(this);
- });
- return this;
- };
- })(window.jQuery);
- }
|