@traveledmap/sticky-js 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/sticky.compile.js +85 -6
- package/dist/sticky.min.js +1 -1
- package/dist/sticky.min.js.gz +0 -0
- package/package.json +1 -1
- package/src/sticky.js +86 -7
package/README.md
CHANGED
|
@@ -120,6 +120,7 @@ Option | Type | Default | Description
|
|
|
120
120
|
------ | ---- | ------- | ----
|
|
121
121
|
data-sticky-wrap | boolean | false | When it's `true` sticky element is wrapped in `<span></span>` which has sticky element dimensions. Prevents content from "jumping".
|
|
122
122
|
data-margin-top | number | 0 | Margin between page and sticky element when scrolled
|
|
123
|
+
data-sticky-height | string/number | null | Optional sticky-state height used for sticky constraint calculations when the element changes size once it becomes sticky
|
|
123
124
|
data-sticky-for | number | 0 | Breakpoint which when is bigger than viewport width, sticky is activated and when is smaller, then sticky is destroyed
|
|
124
125
|
data-sticky-class | string | null | Class added to sticky element when it is stuck
|
|
125
126
|
|
package/dist/sticky.compile.js
CHANGED
|
@@ -39,7 +39,8 @@ var Sticky = /*#__PURE__*/function () {
|
|
|
39
39
|
marginBottom: options.marginBottom || 0,
|
|
40
40
|
stickyFor: options.stickyFor || 0,
|
|
41
41
|
stickyClass: options.stickyClass || null,
|
|
42
|
-
stickyContainer: options.stickyContainer || 'body'
|
|
42
|
+
stickyContainer: options.stickyContainer || 'body',
|
|
43
|
+
stickyHeight: options.stickyHeight || null
|
|
43
44
|
};
|
|
44
45
|
this.updateScrollTopPosition = this.updateScrollTopPosition.bind(this);
|
|
45
46
|
this.scrollDirection = 'down';
|
|
@@ -95,6 +96,7 @@ var Sticky = /*#__PURE__*/function () {
|
|
|
95
96
|
element.sticky.marginBottom = parseInt(element.getAttribute('data-margin-bottom')) || this.options.marginBottom;
|
|
96
97
|
element.sticky.stickyFor = parseInt(element.getAttribute('data-sticky-for')) || this.options.stickyFor;
|
|
97
98
|
element.sticky.stickyClass = element.getAttribute('data-sticky-class') || this.options.stickyClass;
|
|
99
|
+
element.sticky.stickyHeight = element.getAttribute('data-sticky-height') || this.options.stickyHeight;
|
|
98
100
|
element.sticky.wrap = element.hasAttribute('data-sticky-wrap') ? true : this.options.wrap; // @todo attribute for stickyContainer
|
|
99
101
|
// element.sticky.stickyContainer = element.getAttribute('data-sticky-container') || this.options.stickyContainer;
|
|
100
102
|
|
|
@@ -139,7 +141,9 @@ var Sticky = /*#__PURE__*/function () {
|
|
|
139
141
|
}, {
|
|
140
142
|
key: "activate",
|
|
141
143
|
value: function activate(element) {
|
|
142
|
-
|
|
144
|
+
var stickyHeight = this.getStickyStateHeight(element);
|
|
145
|
+
|
|
146
|
+
if (element.sticky.rect.top + stickyHeight < element.sticky.container.rect.top + element.sticky.container.rect.height && element.sticky.stickyFor < this.vp.width && !element.sticky.active) {
|
|
143
147
|
element.sticky.active = true;
|
|
144
148
|
}
|
|
145
149
|
|
|
@@ -199,10 +203,11 @@ var Sticky = /*#__PURE__*/function () {
|
|
|
199
203
|
this.vp = this.getViewportSize();
|
|
200
204
|
this.updateElementRenderedSize(element);
|
|
201
205
|
element.sticky.container.rect = this.getRectangle(element.sticky.container);
|
|
206
|
+
var stickyHeight = this.getStickyStateHeight(element);
|
|
202
207
|
|
|
203
|
-
if (element.sticky.rect.top +
|
|
208
|
+
if (element.sticky.rect.top + stickyHeight < element.sticky.container.rect.top + element.sticky.container.rect.height && element.sticky.stickyFor < this.vp.width && !element.sticky.active) {
|
|
204
209
|
element.sticky.active = true;
|
|
205
|
-
} else if (element.sticky.rect.top +
|
|
210
|
+
} else if (element.sticky.rect.top + stickyHeight >= element.sticky.container.rect.top + element.sticky.container.rect.height || element.sticky.stickyFor >= this.vp.width && element.sticky.active) {
|
|
206
211
|
element.sticky.active = false;
|
|
207
212
|
}
|
|
208
213
|
|
|
@@ -268,8 +273,9 @@ var Sticky = /*#__PURE__*/function () {
|
|
|
268
273
|
top: '',
|
|
269
274
|
left: ''
|
|
270
275
|
});
|
|
276
|
+
var stickyHeight = this.getStickyStateHeight(element);
|
|
271
277
|
|
|
272
|
-
if (this.vp.height <
|
|
278
|
+
if (this.vp.height < stickyHeight || !element.sticky.active) {
|
|
273
279
|
return;
|
|
274
280
|
}
|
|
275
281
|
|
|
@@ -327,7 +333,7 @@ var Sticky = /*#__PURE__*/function () {
|
|
|
327
333
|
left: element.sticky.rect.left + 'px'
|
|
328
334
|
});
|
|
329
335
|
|
|
330
|
-
if (this.scrollTop +
|
|
336
|
+
if (this.scrollTop + stickyHeight + element.sticky.marginTop > element.sticky.container.rect.top + element.sticky.container.offsetHeight - element.sticky.marginBottom) {
|
|
331
337
|
element.sticky.bottomLocked = true;
|
|
332
338
|
this.updateElementRenderedSize(element);
|
|
333
339
|
|
|
@@ -505,6 +511,79 @@ var Sticky = /*#__PURE__*/function () {
|
|
|
505
511
|
height: height
|
|
506
512
|
};
|
|
507
513
|
}
|
|
514
|
+
/**
|
|
515
|
+
* Returns the height that should be used for sticky-state constraint checks.
|
|
516
|
+
* When an explicit stickyHeight is provided, use that target height instead of
|
|
517
|
+
* the currently rendered height to avoid sticky/non-sticky ping-pong.
|
|
518
|
+
* @function
|
|
519
|
+
* @param {node} element - Sticky element
|
|
520
|
+
* @return {number}
|
|
521
|
+
*/
|
|
522
|
+
|
|
523
|
+
}, {
|
|
524
|
+
key: "getStickyStateHeight",
|
|
525
|
+
value: function getStickyStateHeight(element) {
|
|
526
|
+
var explicitStickyHeight = this.resolveStickyHeight(element);
|
|
527
|
+
|
|
528
|
+
if (explicitStickyHeight !== null) {
|
|
529
|
+
return explicitStickyHeight;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return element.sticky.rect.height;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Resolves the configured stickyHeight option/attribute into rendered pixels.
|
|
536
|
+
* The value is measured against a hidden fixed-position element so viewport
|
|
537
|
+
* units and percentages follow the sticky element's fixed positioning rules.
|
|
538
|
+
* @function
|
|
539
|
+
* @param {node} element - Sticky element
|
|
540
|
+
* @return {number|null}
|
|
541
|
+
*/
|
|
542
|
+
|
|
543
|
+
}, {
|
|
544
|
+
key: "resolveStickyHeight",
|
|
545
|
+
value: function resolveStickyHeight(element) {
|
|
546
|
+
var stickyHeight = element.sticky.stickyHeight;
|
|
547
|
+
|
|
548
|
+
if (stickyHeight === null || typeof stickyHeight === 'undefined' || stickyHeight === '') {
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (typeof stickyHeight === 'number') {
|
|
553
|
+
return stickyHeight;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
var numericStickyHeight = Number(stickyHeight);
|
|
557
|
+
|
|
558
|
+
if (!Number.isNaN(numericStickyHeight) && Number.isFinite(numericStickyHeight)) {
|
|
559
|
+
return numericStickyHeight;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (typeof stickyHeight !== 'string') {
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
var measurementElement = document.createElement('div');
|
|
567
|
+
measurementElement.setAttribute('aria-hidden', 'true');
|
|
568
|
+
this.css(measurementElement, {
|
|
569
|
+
position: 'fixed',
|
|
570
|
+
visibility: 'hidden',
|
|
571
|
+
pointerEvents: 'none',
|
|
572
|
+
top: '0',
|
|
573
|
+
left: '0',
|
|
574
|
+
width: '0',
|
|
575
|
+
height: stickyHeight,
|
|
576
|
+
padding: '0',
|
|
577
|
+
border: '0',
|
|
578
|
+
margin: '0',
|
|
579
|
+
boxSizing: 'border-box',
|
|
580
|
+
fontSize: window.getComputedStyle(element).fontSize
|
|
581
|
+
});
|
|
582
|
+
this.body.appendChild(measurementElement);
|
|
583
|
+
var measuredHeight = measurementElement.getBoundingClientRect().height;
|
|
584
|
+
this.body.removeChild(measurementElement);
|
|
585
|
+
return measuredHeight || null;
|
|
586
|
+
}
|
|
508
587
|
/**
|
|
509
588
|
* Updates only the rendered width/height of a sticky element without resetting
|
|
510
589
|
* its stored document position. This keeps the original sticky trigger point
|
package/dist/sticky.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function _classCallCheck(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function _defineProperties(t,e){for(var i=0;i<e.length;i++){var s=e[i];s.enumerable=s.enumerable||!1,s.configurable=!0,"value"in s&&(s.writable=!0),Object.defineProperty(t,s.key,s)}}function _createClass(t,e,i){return e&&_defineProperties(t.prototype,e),i&&_defineProperties(t,i),t}var Sticky=function(){function i(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:"",e=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{};_classCallCheck(this,i),this.selector=t,this.elements=[],this.version="1.3.0",this.vp=this.getViewportSize(),this.body=document.querySelector("body"),this.options={wrap:e.wrap||!1,wrapWith:e.wrapWith||"<span></span>",marginTop:e.marginTop||0,marginBottom:e.marginBottom||0,stickyFor:e.stickyFor||0,stickyClass:e.stickyClass||null,stickyContainer:e.stickyContainer||"body"},this.updateScrollTopPosition=this.updateScrollTopPosition.bind(this),this.scrollDirection="down",this.previousScrollTop=0,this.updateScrollTopPosition(),window.addEventListener("load",this.updateScrollTopPosition),window.addEventListener("scroll",this.updateScrollTopPosition),this.run()}return _createClass(i,[{key:"run",value:function(){var e=this,i=setInterval(function(){if("complete"===document.readyState){clearInterval(i);var t=document.querySelectorAll(e.selector);e.forEach(t,function(t){return e.renderElement(t)})}},10)}},{key:"renderElement",value:function(t){var e=this;t.sticky={},t.sticky.active=!1,t.sticky.hasSyncedStickySize=!1,t.sticky.bottomLocked=!1,t.sticky.syncRenderedSizeTimeout=null,t.sticky.marginTop=parseInt(t.getAttribute("data-margin-top"))||this.options.marginTop,t.sticky.marginBottom=parseInt(t.getAttribute("data-margin-bottom"))||this.options.marginBottom,t.sticky.stickyFor=parseInt(t.getAttribute("data-sticky-for"))||this.options.stickyFor,t.sticky.stickyClass=t.getAttribute("data-sticky-class")||this.options.stickyClass,t.sticky.wrap=!!t.hasAttribute("data-sticky-wrap")||this.options.wrap,t.sticky.stickyContainer=this.options.stickyContainer,t.sticky.container=this.getStickyContainer(t),t.sticky.container.rect=this.getRectangle(t.sticky.container),t.sticky.rect=this.getRectangle(t),"img"===t.tagName.toLowerCase()&&(t.onload=function(){t.sticky.rect=e.getRectangle(t),e.updateElementRenderedSize(t)}),t.sticky.wrap&&this.wrapElement(t),this.activate(t)}},{key:"wrapElement",value:function(t){t.insertAdjacentHTML("beforebegin",t.getAttribute("data-sticky-wrapWith")||this.options.wrapWith),t.previousSibling.appendChild(t)}},{key:"activate",value:function(t){t.sticky.rect.top+t.sticky.rect.height<t.sticky.container.rect.top+t.sticky.container.rect.height&&t.sticky.stickyFor<this.vp.width&&!t.sticky.active&&(t.sticky.active=!0),this.elements.indexOf(t)<0&&this.elements.push(t),t.sticky.resizeEvent||(this.initResizeEvents(t),t.sticky.resizeEvent=!0),t.sticky.scrollEvent||(this.initScrollEvents(t),t.sticky.scrollEvent=!0),this.setPosition(t)}},{key:"initResizeEvents",value:function(t){var e=this;t.sticky.resizeListener=function(){return e.onResizeEvents(t)},window.addEventListener("resize",t.sticky.resizeListener)}},{key:"destroyResizeEvents",value:function(t){window.removeEventListener("resize",t.sticky.resizeListener)}},{key:"onResizeEvents",value:function(t){this.vp=this.getViewportSize(),this.updateElementRenderedSize(t),t.sticky.container.rect=this.getRectangle(t.sticky.container),t.sticky.rect.top+t.sticky.rect.height<t.sticky.container.rect.top+t.sticky.container.rect.height&&t.sticky.stickyFor<this.vp.width&&!t.sticky.active?t.sticky.active=!0:(t.sticky.rect.top+t.sticky.rect.height>=t.sticky.container.rect.top+t.sticky.container.rect.height||t.sticky.stickyFor>=this.vp.width&&t.sticky.active)&&(t.sticky.active=!1),this.setPosition(t)}},{key:"initScrollEvents",value:function(t){var e=this;t.sticky.scrollListener=function(){return e.onScrollEvents(t)},window.addEventListener("scroll",t.sticky.scrollListener)}},{key:"destroyScrollEvents",value:function(t){window.removeEventListener("scroll",t.sticky.scrollListener)}},{key:"onScrollEvents",value:function(t){t.sticky&&t.sticky.active&&this.setPosition(t)}},{key:"setPosition",value:function(t){if(!t.isDisabled&&(this.css(t,{position:"",width:"",top:"",left:""}),!(this.vp.height<t.sticky.rect.height)&&t.sticky.active))if(t.sticky.rect.width||(t.sticky.rect=this.getRectangle(t)),t.sticky.wrap&&this.css(t.parentNode,{display:"block",width:t.sticky.rect.width+"px",height:t.sticky.rect.height+"px"}),0===t.sticky.rect.top&&t.sticky.container===this.body){if(this.css(t,{position:"fixed",top:t.sticky.rect.top+"px",left:t.sticky.rect.left+"px",width:t.sticky.rect.width+"px"}),t.sticky.stickyClass&&t.classList.add(t.sticky.stickyClass),t.sticky.bottomLocked=!1,this.syncRenderedSize(t))return}else if(this.scrollTop>t.sticky.rect.top-t.sticky.marginTop){if(t.sticky.bottomLocked&&"up"!==this.scrollDirection)return this.updateElementRenderedSize(t),this.css(t,{position:"fixed",width:t.sticky.rect.width+"px",left:t.sticky.rect.left+"px",top:t.sticky.container.rect.top+t.sticky.container.offsetHeight-(this.scrollTop+t.sticky.rect.height+t.sticky.marginBottom)+"px"}),void(t.sticky.stickyClass&&t.classList.remove(t.sticky.stickyClass));if(this.css(t,{position:"fixed",width:t.sticky.rect.width+"px",left:t.sticky.rect.left+"px"}),this.scrollTop+t.sticky.rect.height+t.sticky.marginTop>t.sticky.container.rect.top+t.sticky.container.offsetHeight-t.sticky.marginBottom){if(t.sticky.bottomLocked=!0,this.updateElementRenderedSize(t),t.sticky.stickyClass&&t.classList.remove(t.sticky.stickyClass),this.css(t,{top:t.sticky.container.rect.top+t.sticky.container.offsetHeight-(this.scrollTop+t.sticky.rect.height+t.sticky.marginBottom)+"px"}),this.syncRenderedSize(t))return}else if("up"===this.scrollDirection&&(t.sticky.bottomLocked=!1),t.sticky.stickyClass&&t.classList.add(t.sticky.stickyClass),this.css(t,{top:t.sticky.marginTop+"px"}),this.syncRenderedSize(t))return}else t.sticky.hasSyncedStickySize=!1,t.sticky.bottomLocked=!1,this.clearScheduledRenderedSizeSync(t),t.sticky.stickyClass&&t.classList.remove(t.sticky.stickyClass),this.css(t,{position:"",width:"",top:"",left:""}),t.sticky.wrap&&this.css(t.parentNode,{display:"",width:"",height:""})}},{key:"update",value:function(){var e=this;this.forEach(this.elements,function(t){e.updateElementRenderedSize(t),t.sticky.container.rect=e.getRectangle(t.sticky.container),e.activate(t),e.setPosition(t)})}},{key:"destroy",value:function(){var e=this;window.removeEventListener("load",this.updateScrollTopPosition),window.removeEventListener("scroll",this.updateScrollTopPosition),this.forEach(this.elements,function(t){e.clearScheduledRenderedSizeSync(t),e.destroyResizeEvents(t),e.destroyScrollEvents(t),delete t.sticky})}},{key:"enable",value:function(){var e=this;this.forEach(this.elements,function(t){t.isDisabled=!1,e.setPosition(t)})}},{key:"disable",value:function(){var e=this;this.forEach(this.elements,function(t){t.isDisabled=!0,e.css(t,{position:"",width:"",top:"",left:""})})}},{key:"getStickyContainer",value:function(t){for(var e=t.parentNode;!e.hasAttribute("data-sticky-container")&&!e.parentNode.querySelector(t.sticky.stickyContainer)&&e!==this.body;)e=e.parentNode;return e}},{key:"getRectangle",value:function(t){this.css(t,{position:"",width:"",top:"",left:""});for(var e=Math.max(t.offsetWidth,t.clientWidth,t.scrollWidth),i=Math.max(t.offsetHeight,t.clientHeight,t.scrollHeight),s=0,c=0;s+=t.offsetTop||0,c+=t.offsetLeft||0,t=t.offsetParent;);return{top:s,left:c,width:e,height:i}}},{key:"updateElementRenderedSize",value:function(t){var e=t.getBoundingClientRect();t.sticky.rect||(t.sticky.rect=this.getRectangle(t)),t.sticky.rect.width=e.width,t.sticky.rect.height=e.height}},{key:"syncRenderedSize",value:function(t){if(t.sticky.hasSyncedStickySize)return!1;if(t.sticky.syncRenderedSizeTimeout)return!0;var e=t.getBoundingClientRect(),i=.5<Math.abs(e.width-t.sticky.rect.width),s=.5<Math.abs(e.height-t.sticky.rect.height);return(i||s)&&(!t.sticky.isSyncingRenderedSize&&(this.scheduleRenderedSizeSync(t),!0))}},{key:"scheduleRenderedSizeSync",value:function(e){var i=this;e.sticky.syncRenderedSizeTimeout=window.setTimeout(function(){if(e.sticky.syncRenderedSizeTimeout=null,e.sticky&&!e.isDisabled){var t=e.getBoundingClientRect();e.sticky.rect.width=t.width,e.sticky.rect.height=t.height,e.sticky.container.rect=i.getRectangle(e.sticky.container),e.sticky.hasSyncedStickySize=!0,e.sticky.isSyncingRenderedSize=!0,i.setPosition(e),e.sticky.isSyncingRenderedSize=!1}},500)}},{key:"clearScheduledRenderedSizeSync",value:function(t){t.sticky&&t.sticky.syncRenderedSizeTimeout&&(window.clearTimeout(t.sticky.syncRenderedSizeTimeout),t.sticky.syncRenderedSizeTimeout=null)}},{key:"getViewportSize",value:function(){return{width:Math.max(document.documentElement.clientWidth,window.innerWidth||0),height:Math.max(document.documentElement.clientHeight,window.innerHeight||0)}}},{key:"updateScrollTopPosition",value:function(){this.scrollTop=(window.pageYOffset||document.scrollTop)-(document.clientTop||0)||0,this.scrollDirection=this.scrollTop>=this.previousScrollTop?"down":"up",this.previousScrollTop=this.scrollTop}},{key:"forEach",value:function(t,e){for(var i=0,s=t.length;i<s;i++)e(t[i])}},{key:"css",value:function(t,e){for(var i in e)e.hasOwnProperty(i)&&(t.style[i]=e[i])}}]),i}();!function(t,e){"undefined"!=typeof exports?module.exports=e:"function"==typeof define&&define.amd?define([],function(){return e}):t.Sticky=e}(this,Sticky);
|
|
1
|
+
function _classCallCheck(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function _defineProperties(t,e){for(var i=0;i<e.length;i++){var s=e[i];s.enumerable=s.enumerable||!1,s.configurable=!0,"value"in s&&(s.writable=!0),Object.defineProperty(t,s.key,s)}}function _createClass(t,e,i){return e&&_defineProperties(t.prototype,e),i&&_defineProperties(t,i),t}var Sticky=function(){function i(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:"",e=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{};_classCallCheck(this,i),this.selector=t,this.elements=[],this.version="1.3.0",this.vp=this.getViewportSize(),this.body=document.querySelector("body"),this.options={wrap:e.wrap||!1,wrapWith:e.wrapWith||"<span></span>",marginTop:e.marginTop||0,marginBottom:e.marginBottom||0,stickyFor:e.stickyFor||0,stickyClass:e.stickyClass||null,stickyContainer:e.stickyContainer||"body",stickyHeight:e.stickyHeight||null},this.updateScrollTopPosition=this.updateScrollTopPosition.bind(this),this.scrollDirection="down",this.previousScrollTop=0,this.updateScrollTopPosition(),window.addEventListener("load",this.updateScrollTopPosition),window.addEventListener("scroll",this.updateScrollTopPosition),this.run()}return _createClass(i,[{key:"run",value:function(){var e=this,i=setInterval(function(){if("complete"===document.readyState){clearInterval(i);var t=document.querySelectorAll(e.selector);e.forEach(t,function(t){return e.renderElement(t)})}},10)}},{key:"renderElement",value:function(t){var e=this;t.sticky={},t.sticky.active=!1,t.sticky.hasSyncedStickySize=!1,t.sticky.bottomLocked=!1,t.sticky.syncRenderedSizeTimeout=null,t.sticky.marginTop=parseInt(t.getAttribute("data-margin-top"))||this.options.marginTop,t.sticky.marginBottom=parseInt(t.getAttribute("data-margin-bottom"))||this.options.marginBottom,t.sticky.stickyFor=parseInt(t.getAttribute("data-sticky-for"))||this.options.stickyFor,t.sticky.stickyClass=t.getAttribute("data-sticky-class")||this.options.stickyClass,t.sticky.stickyHeight=t.getAttribute("data-sticky-height")||this.options.stickyHeight,t.sticky.wrap=!!t.hasAttribute("data-sticky-wrap")||this.options.wrap,t.sticky.stickyContainer=this.options.stickyContainer,t.sticky.container=this.getStickyContainer(t),t.sticky.container.rect=this.getRectangle(t.sticky.container),t.sticky.rect=this.getRectangle(t),"img"===t.tagName.toLowerCase()&&(t.onload=function(){t.sticky.rect=e.getRectangle(t),e.updateElementRenderedSize(t)}),t.sticky.wrap&&this.wrapElement(t),this.activate(t)}},{key:"wrapElement",value:function(t){t.insertAdjacentHTML("beforebegin",t.getAttribute("data-sticky-wrapWith")||this.options.wrapWith),t.previousSibling.appendChild(t)}},{key:"activate",value:function(t){var e=this.getStickyStateHeight(t);t.sticky.rect.top+e<t.sticky.container.rect.top+t.sticky.container.rect.height&&t.sticky.stickyFor<this.vp.width&&!t.sticky.active&&(t.sticky.active=!0),this.elements.indexOf(t)<0&&this.elements.push(t),t.sticky.resizeEvent||(this.initResizeEvents(t),t.sticky.resizeEvent=!0),t.sticky.scrollEvent||(this.initScrollEvents(t),t.sticky.scrollEvent=!0),this.setPosition(t)}},{key:"initResizeEvents",value:function(t){var e=this;t.sticky.resizeListener=function(){return e.onResizeEvents(t)},window.addEventListener("resize",t.sticky.resizeListener)}},{key:"destroyResizeEvents",value:function(t){window.removeEventListener("resize",t.sticky.resizeListener)}},{key:"onResizeEvents",value:function(t){this.vp=this.getViewportSize(),this.updateElementRenderedSize(t),t.sticky.container.rect=this.getRectangle(t.sticky.container);var e=this.getStickyStateHeight(t);t.sticky.rect.top+e<t.sticky.container.rect.top+t.sticky.container.rect.height&&t.sticky.stickyFor<this.vp.width&&!t.sticky.active?t.sticky.active=!0:(t.sticky.rect.top+e>=t.sticky.container.rect.top+t.sticky.container.rect.height||t.sticky.stickyFor>=this.vp.width&&t.sticky.active)&&(t.sticky.active=!1),this.setPosition(t)}},{key:"initScrollEvents",value:function(t){var e=this;t.sticky.scrollListener=function(){return e.onScrollEvents(t)},window.addEventListener("scroll",t.sticky.scrollListener)}},{key:"destroyScrollEvents",value:function(t){window.removeEventListener("scroll",t.sticky.scrollListener)}},{key:"onScrollEvents",value:function(t){t.sticky&&t.sticky.active&&this.setPosition(t)}},{key:"setPosition",value:function(t){if(!t.isDisabled){this.css(t,{position:"",width:"",top:"",left:""});var e=this.getStickyStateHeight(t);if(!(this.vp.height<e)&&t.sticky.active)if(t.sticky.rect.width||(t.sticky.rect=this.getRectangle(t)),t.sticky.wrap&&this.css(t.parentNode,{display:"block",width:t.sticky.rect.width+"px",height:t.sticky.rect.height+"px"}),0===t.sticky.rect.top&&t.sticky.container===this.body){if(this.css(t,{position:"fixed",top:t.sticky.rect.top+"px",left:t.sticky.rect.left+"px",width:t.sticky.rect.width+"px"}),t.sticky.stickyClass&&t.classList.add(t.sticky.stickyClass),t.sticky.bottomLocked=!1,this.syncRenderedSize(t))return}else if(this.scrollTop>t.sticky.rect.top-t.sticky.marginTop){if(t.sticky.bottomLocked&&"up"!==this.scrollDirection)return this.updateElementRenderedSize(t),this.css(t,{position:"fixed",width:t.sticky.rect.width+"px",left:t.sticky.rect.left+"px",top:t.sticky.container.rect.top+t.sticky.container.offsetHeight-(this.scrollTop+t.sticky.rect.height+t.sticky.marginBottom)+"px"}),void(t.sticky.stickyClass&&t.classList.remove(t.sticky.stickyClass));if(this.css(t,{position:"fixed",width:t.sticky.rect.width+"px",left:t.sticky.rect.left+"px"}),this.scrollTop+e+t.sticky.marginTop>t.sticky.container.rect.top+t.sticky.container.offsetHeight-t.sticky.marginBottom){if(t.sticky.bottomLocked=!0,this.updateElementRenderedSize(t),t.sticky.stickyClass&&t.classList.remove(t.sticky.stickyClass),this.css(t,{top:t.sticky.container.rect.top+t.sticky.container.offsetHeight-(this.scrollTop+t.sticky.rect.height+t.sticky.marginBottom)+"px"}),this.syncRenderedSize(t))return}else if("up"===this.scrollDirection&&(t.sticky.bottomLocked=!1),t.sticky.stickyClass&&t.classList.add(t.sticky.stickyClass),this.css(t,{top:t.sticky.marginTop+"px"}),this.syncRenderedSize(t))return}else t.sticky.hasSyncedStickySize=!1,t.sticky.bottomLocked=!1,this.clearScheduledRenderedSizeSync(t),t.sticky.stickyClass&&t.classList.remove(t.sticky.stickyClass),this.css(t,{position:"",width:"",top:"",left:""}),t.sticky.wrap&&this.css(t.parentNode,{display:"",width:"",height:""})}}},{key:"update",value:function(){var e=this;this.forEach(this.elements,function(t){e.updateElementRenderedSize(t),t.sticky.container.rect=e.getRectangle(t.sticky.container),e.activate(t),e.setPosition(t)})}},{key:"destroy",value:function(){var e=this;window.removeEventListener("load",this.updateScrollTopPosition),window.removeEventListener("scroll",this.updateScrollTopPosition),this.forEach(this.elements,function(t){e.clearScheduledRenderedSizeSync(t),e.destroyResizeEvents(t),e.destroyScrollEvents(t),delete t.sticky})}},{key:"enable",value:function(){var e=this;this.forEach(this.elements,function(t){t.isDisabled=!1,e.setPosition(t)})}},{key:"disable",value:function(){var e=this;this.forEach(this.elements,function(t){t.isDisabled=!0,e.css(t,{position:"",width:"",top:"",left:""})})}},{key:"getStickyContainer",value:function(t){for(var e=t.parentNode;!e.hasAttribute("data-sticky-container")&&!e.parentNode.querySelector(t.sticky.stickyContainer)&&e!==this.body;)e=e.parentNode;return e}},{key:"getRectangle",value:function(t){this.css(t,{position:"",width:"",top:"",left:""});for(var e=Math.max(t.offsetWidth,t.clientWidth,t.scrollWidth),i=Math.max(t.offsetHeight,t.clientHeight,t.scrollHeight),s=0,n=0;s+=t.offsetTop||0,n+=t.offsetLeft||0,t=t.offsetParent;);return{top:s,left:n,width:e,height:i}}},{key:"getStickyStateHeight",value:function(t){var e=this.resolveStickyHeight(t);return null!==e?e:t.sticky.rect.height}},{key:"resolveStickyHeight",value:function(t){var e=t.sticky.stickyHeight;if(null==e||""===e)return null;if("number"==typeof e)return e;var i=Number(e);if(!Number.isNaN(i)&&Number.isFinite(i))return i;if("string"!=typeof e)return null;var s=document.createElement("div");s.setAttribute("aria-hidden","true"),this.css(s,{position:"fixed",visibility:"hidden",pointerEvents:"none",top:"0",left:"0",width:"0",height:e,padding:"0",border:"0",margin:"0",boxSizing:"border-box",fontSize:window.getComputedStyle(t).fontSize}),this.body.appendChild(s);var n=s.getBoundingClientRect().height;return this.body.removeChild(s),n||null}},{key:"updateElementRenderedSize",value:function(t){var e=t.getBoundingClientRect();t.sticky.rect||(t.sticky.rect=this.getRectangle(t)),t.sticky.rect.width=e.width,t.sticky.rect.height=e.height}},{key:"syncRenderedSize",value:function(t){if(t.sticky.hasSyncedStickySize)return!1;if(t.sticky.syncRenderedSizeTimeout)return!0;var e=t.getBoundingClientRect(),i=.5<Math.abs(e.width-t.sticky.rect.width),s=.5<Math.abs(e.height-t.sticky.rect.height);return(i||s)&&(!t.sticky.isSyncingRenderedSize&&(this.scheduleRenderedSizeSync(t),!0))}},{key:"scheduleRenderedSizeSync",value:function(e){var i=this;e.sticky.syncRenderedSizeTimeout=window.setTimeout(function(){if(e.sticky.syncRenderedSizeTimeout=null,e.sticky&&!e.isDisabled){var t=e.getBoundingClientRect();e.sticky.rect.width=t.width,e.sticky.rect.height=t.height,e.sticky.container.rect=i.getRectangle(e.sticky.container),e.sticky.hasSyncedStickySize=!0,e.sticky.isSyncingRenderedSize=!0,i.setPosition(e),e.sticky.isSyncingRenderedSize=!1}},500)}},{key:"clearScheduledRenderedSizeSync",value:function(t){t.sticky&&t.sticky.syncRenderedSizeTimeout&&(window.clearTimeout(t.sticky.syncRenderedSizeTimeout),t.sticky.syncRenderedSizeTimeout=null)}},{key:"getViewportSize",value:function(){return{width:Math.max(document.documentElement.clientWidth,window.innerWidth||0),height:Math.max(document.documentElement.clientHeight,window.innerHeight||0)}}},{key:"updateScrollTopPosition",value:function(){this.scrollTop=(window.pageYOffset||document.scrollTop)-(document.clientTop||0)||0,this.scrollDirection=this.scrollTop>=this.previousScrollTop?"down":"up",this.previousScrollTop=this.scrollTop}},{key:"forEach",value:function(t,e){for(var i=0,s=t.length;i<s;i++)e(t[i])}},{key:"css",value:function(t,e){for(var i in e)e.hasOwnProperty(i)&&(t.style[i]=e[i])}}]),i}();!function(t,e){"undefined"!=typeof exports?module.exports=e:"function"==typeof define&&define.amd?define([],function(){return e}):t.Sticky=e}(this,Sticky);
|
package/dist/sticky.min.js.gz
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/sticky.js
CHANGED
|
@@ -34,6 +34,7 @@ class Sticky {
|
|
|
34
34
|
stickyFor: options.stickyFor || 0,
|
|
35
35
|
stickyClass: options.stickyClass || null,
|
|
36
36
|
stickyContainer: options.stickyContainer || 'body',
|
|
37
|
+
stickyHeight: options.stickyHeight || null,
|
|
37
38
|
};
|
|
38
39
|
|
|
39
40
|
this.updateScrollTopPosition = this.updateScrollTopPosition.bind(this);
|
|
@@ -85,6 +86,7 @@ class Sticky {
|
|
|
85
86
|
element.sticky.marginBottom = parseInt(element.getAttribute('data-margin-bottom')) || this.options.marginBottom;
|
|
86
87
|
element.sticky.stickyFor = parseInt(element.getAttribute('data-sticky-for')) || this.options.stickyFor;
|
|
87
88
|
element.sticky.stickyClass = element.getAttribute('data-sticky-class') || this.options.stickyClass;
|
|
89
|
+
element.sticky.stickyHeight = element.getAttribute('data-sticky-height') || this.options.stickyHeight;
|
|
88
90
|
element.sticky.wrap = element.hasAttribute('data-sticky-wrap') ? true : this.options.wrap;
|
|
89
91
|
// @todo attribute for stickyContainer
|
|
90
92
|
// element.sticky.stickyContainer = element.getAttribute('data-sticky-container') || this.options.stickyContainer;
|
|
@@ -128,9 +130,11 @@ class Sticky {
|
|
|
128
130
|
* @function
|
|
129
131
|
* @param {node} element - Element to be activated
|
|
130
132
|
*/
|
|
131
|
-
|
|
133
|
+
activate(element) {
|
|
134
|
+
const stickyHeight = this.getStickyStateHeight(element);
|
|
135
|
+
|
|
132
136
|
if (
|
|
133
|
-
((element.sticky.rect.top +
|
|
137
|
+
((element.sticky.rect.top + stickyHeight) < (element.sticky.container.rect.top + element.sticky.container.rect.height))
|
|
134
138
|
&& (element.sticky.stickyFor < this.vp.width)
|
|
135
139
|
&& !element.sticky.active
|
|
136
140
|
) {
|
|
@@ -181,20 +185,21 @@ class Sticky {
|
|
|
181
185
|
* @function
|
|
182
186
|
* @param {node} element - Element for which event function is fired
|
|
183
187
|
*/
|
|
184
|
-
|
|
188
|
+
onResizeEvents(element) {
|
|
185
189
|
this.vp = this.getViewportSize();
|
|
186
190
|
|
|
187
191
|
this.updateElementRenderedSize(element);
|
|
188
192
|
element.sticky.container.rect = this.getRectangle(element.sticky.container);
|
|
193
|
+
const stickyHeight = this.getStickyStateHeight(element);
|
|
189
194
|
|
|
190
195
|
if (
|
|
191
|
-
((element.sticky.rect.top +
|
|
196
|
+
((element.sticky.rect.top + stickyHeight) < (element.sticky.container.rect.top + element.sticky.container.rect.height))
|
|
192
197
|
&& (element.sticky.stickyFor < this.vp.width)
|
|
193
198
|
&& !element.sticky.active
|
|
194
199
|
) {
|
|
195
200
|
element.sticky.active = true;
|
|
196
201
|
} else if (
|
|
197
|
-
((element.sticky.rect.top +
|
|
202
|
+
((element.sticky.rect.top + stickyHeight) >= (element.sticky.container.rect.top + element.sticky.container.rect.height))
|
|
198
203
|
|| element.sticky.stickyFor >= this.vp.width
|
|
199
204
|
&& element.sticky.active
|
|
200
205
|
) {
|
|
@@ -249,7 +254,9 @@ class Sticky {
|
|
|
249
254
|
}
|
|
250
255
|
this.css(element, { position: '', width: '', top: '', left: '' });
|
|
251
256
|
|
|
252
|
-
|
|
257
|
+
const stickyHeight = this.getStickyStateHeight(element);
|
|
258
|
+
|
|
259
|
+
if ((this.vp.height < stickyHeight) || !element.sticky.active) {
|
|
253
260
|
return;
|
|
254
261
|
}
|
|
255
262
|
|
|
@@ -310,7 +317,7 @@ class Sticky {
|
|
|
310
317
|
});
|
|
311
318
|
|
|
312
319
|
if (
|
|
313
|
-
(this.scrollTop +
|
|
320
|
+
(this.scrollTop + stickyHeight + element.sticky.marginTop)
|
|
314
321
|
> (element.sticky.container.rect.top + element.sticky.container.offsetHeight - element.sticky.marginBottom)
|
|
315
322
|
) {
|
|
316
323
|
element.sticky.bottomLocked = true;
|
|
@@ -450,6 +457,78 @@ class Sticky {
|
|
|
450
457
|
}
|
|
451
458
|
|
|
452
459
|
|
|
460
|
+
/**
|
|
461
|
+
* Returns the height that should be used for sticky-state constraint checks.
|
|
462
|
+
* When an explicit stickyHeight is provided, use that target height instead of
|
|
463
|
+
* the currently rendered height to avoid sticky/non-sticky ping-pong.
|
|
464
|
+
* @function
|
|
465
|
+
* @param {node} element - Sticky element
|
|
466
|
+
* @return {number}
|
|
467
|
+
*/
|
|
468
|
+
getStickyStateHeight(element) {
|
|
469
|
+
const explicitStickyHeight = this.resolveStickyHeight(element);
|
|
470
|
+
|
|
471
|
+
if (explicitStickyHeight !== null) {
|
|
472
|
+
return explicitStickyHeight;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return element.sticky.rect.height;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Resolves the configured stickyHeight option/attribute into rendered pixels.
|
|
481
|
+
* The value is measured against a hidden fixed-position element so viewport
|
|
482
|
+
* units and percentages follow the sticky element's fixed positioning rules.
|
|
483
|
+
* @function
|
|
484
|
+
* @param {node} element - Sticky element
|
|
485
|
+
* @return {number|null}
|
|
486
|
+
*/
|
|
487
|
+
resolveStickyHeight(element) {
|
|
488
|
+
const { stickyHeight } = element.sticky;
|
|
489
|
+
|
|
490
|
+
if (stickyHeight === null || typeof stickyHeight === 'undefined' || stickyHeight === '') {
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (typeof stickyHeight === 'number') {
|
|
495
|
+
return stickyHeight;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const numericStickyHeight = Number(stickyHeight);
|
|
499
|
+
if (!Number.isNaN(numericStickyHeight) && Number.isFinite(numericStickyHeight)) {
|
|
500
|
+
return numericStickyHeight;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (typeof stickyHeight !== 'string') {
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const measurementElement = document.createElement('div');
|
|
508
|
+
measurementElement.setAttribute('aria-hidden', 'true');
|
|
509
|
+
this.css(measurementElement, {
|
|
510
|
+
position: 'fixed',
|
|
511
|
+
visibility: 'hidden',
|
|
512
|
+
pointerEvents: 'none',
|
|
513
|
+
top: '0',
|
|
514
|
+
left: '0',
|
|
515
|
+
width: '0',
|
|
516
|
+
height: stickyHeight,
|
|
517
|
+
padding: '0',
|
|
518
|
+
border: '0',
|
|
519
|
+
margin: '0',
|
|
520
|
+
boxSizing: 'border-box',
|
|
521
|
+
fontSize: window.getComputedStyle(element).fontSize,
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
this.body.appendChild(measurementElement);
|
|
525
|
+
const measuredHeight = measurementElement.getBoundingClientRect().height;
|
|
526
|
+
this.body.removeChild(measurementElement);
|
|
527
|
+
|
|
528
|
+
return measuredHeight || null;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
|
|
453
532
|
/**
|
|
454
533
|
* Updates only the rendered width/height of a sticky element without resetting
|
|
455
534
|
* its stored document position. This keeps the original sticky trigger point
|